# ServiceNow SDK Samples The ServiceNow SDK provides a TypeScript-based fluent interface for building applications on the ServiceNow platform. This repository contains sample code demonstrating how to create tables, records, business rules, REST APIs, flows, service catalog items, UI pages, and more using the SDK's declarative syntax. Each sample project illustrates specific platform capabilities through practical, working examples. The SDK uses a fluent API pattern where developers define ServiceNow artifacts using TypeScript functions like `Table()`, `BusinessRule()`, `RestApi()`, and `Flow()`. Scripts can be embedded inline or referenced from external files using `Now.include()`. The SDK compiles these definitions into deployable application packages that can be pushed to ServiceNow instances. ## Table API Create custom tables with typed schema columns including strings, integers, booleans, dates, and references to other tables. ```typescript import { Table, StringColumn, IntegerColumn, BooleanColumn, DateColumn, ReferenceColumn } from '@servicenow/sdk/core' // Create a simple table with multiple column types export const x_tablesample_name = Table({ name: 'x_tablesample_name', schema: { string_column: StringColumn({ mandatory: true, label: 'String Column' }), integer_column: IntegerColumn({ mandatory: true, label: 'Integer Column' }), boolean_column: BooleanColumn({ mandatory: true }), date_column: DateColumn({ mandatory: true }), }, }) // Create a table that extends task with auto-numbering export const x_tablesample_extends = Table({ name: 'x_tablesample_extends', extends: 'task', extensible: true, display: 'Extension Example Table', auto_number: { prefix: 'sample', number: 100, number_of_digits: 9, }, schema: { user_reference_column: ReferenceColumn({ mandatory: true, label: 'User Reference', referenceTable: 'sys_user', }), }, }) // Create a table with dropdown choices and indexes export const x_tablesample_index = Table({ name: 'x_tablesample_index', schema: { name: StringColumn({ label: 'Name', mandatory: true }), color: StringColumn({ label: 'Color', dropdown: 'suggestion', mandatory: true, choices: { white: { label: 'White' }, brown: { label: 'Brown' }, black: { label: 'Black' }, }, }), owner: ReferenceColumn({ mandatory: true, label: 'User', referenceTable: 'sys_user' }), }, display: 'name', index: [{ element: 'color', name: 'color_index', unique: false }], }) ``` ## Record API Insert data records into tables, including demo data and references to existing records. ```typescript import { Record } from '@servicenow/sdk/core' // Create a record in a custom table Record({ table: 'x_helloworld_tableone', $id: Now.ID['x_helloworld_tableone_record1'], data: { string_field: 'Hello World 1', datetime_field: '01-01-2024 12:00:00', integer_field: 1, }, }) // Create an incident record as demo data Record({ $id: Now.ID['incident-1'], table: 'incident', data: { number: 'INC0010001', active: true, short_description: 'This is a sample incident', description: 'This is a sample incident description', comments: 'This is a sample comment', cmdb_ci: '265e09dbeb584baf9c111a7148c99529', urgency: 1, priority: 3, opened_at: '2025-01-01 12:30:00', }, $meta: { installMethod: 'demo', }, }) // Create a portal record with file attachments Record({ $id: Now.ID['sample-portal'], table: 'sp_portal', data: { title: 'Sample Portal', url_suffix: 'sample', logo: Now.attach('../../../assets/servicenow.svg'), icon: Now.attach('../../../assets/servicenow.jpg'), }, }) ``` ## Business Rule API Define server-side logic that executes when records are inserted, updated, or deleted. ```typescript import { BusinessRule } from '@servicenow/sdk/core' // Create a business rule with external script file BusinessRule({ $id: Now.ID['br1'], name: 'Sample Business Rule', active: true, table: 'sc_req_item', when: 'before', script: Now.include('./business-rule-1.server.js'), }) // business-rule-1.server.js ;(function executeRule(current, previous) { // Add your business logic here current.short_description = 'Updated by business rule' }) ``` ## REST API Create custom REST endpoints with multiple HTTP methods and inline or modular script handlers. ```typescript import { RestApi } from '@servicenow/sdk/core' // Create a REST API with inline scripts for all HTTP methods RestApi({ $id: Now.ID['restapi-hello'], name: 'rest api fluent sample', service_id: 'restapi_hello', consumes: 'application/json', routes: [ { $id: Now.ID['restapi-hello-get'], name: 'get', method: 'GET', script: script` (function process(request, response) { response.setBody({ message: 'Hello, World!' }) })(request, response) `, }, { $id: Now.ID['restapi-hello-post'], name: 'post', method: 'POST', script: script` (function process(request, response) { var reqbody = request.body.dataString; var parser = new global.JSON(); var parsedData = parser.decode(reqbody); response.setBody({ post: parsedData }) })(request, response) `, }, { $id: Now.ID['restapi-hello-delete'], name: 'delete', method: 'DELETE', script: script` (function process(request, response) { response.setBody({ delete: { msg: "DELETED" } }) })(request, response) `, }, ], }) // Create a REST API with modular script handlers import { process } from '../server/rest-api-handler' RestApi({ $id: Now.ID['restapi-modules'], name: 'rest api fluent modules sample', service_id: 'restapi_modules', consumes: 'application/json', routes: [ { $id: Now.ID['restapi-modules-get'], name: 'get', method: 'GET', script: process, }, ], }) // rest-api-handler.js export function process(request, response) { response.setStatus(200) response.setBody({ method: "GET", data: {"msg" : "success"}, }) } ``` ## ACL API Define access control lists to restrict record operations based on roles, conditions, and security attributes. ```typescript import { Acl, Role } from '@servicenow/sdk/core' // Create a custom role export const sampleAdmin = Role({ name: 'x_acl_sample.admin' }) // Create ACL - role-based access Acl({ $id: Now.ID['create_acl'], localOrExisting: 'Existing', type: 'record', operation: 'create', roles: [sampleAdmin, 'x_other_scope.manager'], table: 'x_acl_sample_table', }) // Write ACL - single role Acl({ $id: Now.ID['write_acl'], localOrExisting: 'Existing', type: 'record', operation: 'write', roles: [sampleAdmin], table: 'x_acl_sample_table', }) // Delete ACL - security attribute Acl({ $id: Now.ID['delete_acl'], localOrExisting: 'Existing', type: 'record', operation: 'delete', table: 'x_acl_sample_table', securityAttribute: 'has_admin_role' }) // Read ACL - condition-based Acl({ $id: Now.ID['read_acl'], localOrExisting: 'Existing', type: 'record', operation: 'read', table: 'x_acl_sample_table', condition: 'field=value^active=true' }) // REST endpoint ACL Acl({ $id: Now.ID['rest_acl'], name: 'sample_api', type: 'rest_endpoint', operation: 'execute', roles: [sampleAdmin], securityAttribute: 'user_is_authenticated' }) ``` ## Client Script API Create client-side scripts that run in the browser on form load or field change events. ```typescript import { ClientScript } from '@servicenow/sdk/core' // Create an onLoad client script export default ClientScript({ $id: Now.ID['sample1'], type: 'onLoad', ui_type: 'all', table: 'incident', global: true, name: 'sample_client_script', active: true, applies_extended: false, description: 'sample client script', isolate_script: false, script: Now.include('./clientscript.client.js') }) // clientscript.client.js function onLoad() { g_form.addInfoMessage('Hello from Fluent Client Script') } ``` ## Script Include API Create reusable server-side JavaScript classes that can be called from business rules, scripts, and APIs. ```typescript import { ScriptInclude } from '@servicenow/sdk/core' ScriptInclude({ $id: Now.ID['my-script-include'], name: 'MyScriptInclude', active: true, apiName: 'x_scriptincludes.MyScriptInclude', script: Now.include('./MyScriptInclude.server.js'), }) // MyScriptInclude.server.js var MyScriptInclude = Class.create() MyScriptInclude.prototype = { initialize: function () {}, example: function () { const processor = new global.AbstractAjaxProcessor() gs.info('This is an example script include method') }, type: 'MyScriptInclude', } ``` ## UI Action API Create buttons and links on forms and lists that execute server or client-side scripts. ```typescript import { UiAction } from '@servicenow/sdk/core' UiAction({ $id: Now.ID['car_info'], table: 'x_uiactionsample_ts_custom_cars', actionName: 'Car Information', name: 'View car info', active: true, showInsert: true, showUpdate: true, hint: 'View car info', condition: "current.type == 'SUV'", form: { showButton: true, showLink: true, showContextMenu: false, style: 'destructive', }, list: { showLink: true, style: 'primary', showButton: true, showContextMenu: false, showListChoice: false, showBannerButton: true, showSaveWithFormButton: true, }, workspace: { isConfigurableWorkspace: true, showFormButtonV2: true, showFormMenuButtonV2: true, clientScriptV2: `function onClick(g_form) { }`, }, script: `current.name = "updated by script"; current.update();`, roles: ['u_requestor'], client: { isClient: true, isUi11Compatible: true, isUi16Compatible: true, }, order: 100, includeInViews: ['specialView'], }) ``` ## Flow Designer API Create automated workflows with triggers, actions, and conditional logic using the Flow Designer API. ```typescript import { action, Flow, wfa, trigger } from '@servicenow/sdk/automation' // Flow triggered on record creation export const incidentSeverityAlertFlow = Flow( { $id: Now.ID['incident_severity_alert_flow'], name: 'Incident Severity Alert Flow', description: 'Alerts managers based on incident severity', }, wfa.trigger( trigger.record.created, { $id: Now.ID['empty_origin_incident_trigger'] }, { table: 'incident', condition: 'origin=NULL', run_flow_in: 'background', run_on_extended: 'false', run_when_setting: 'both', run_when_user_setting: 'any', run_when_user_list: [], } ), (params) => { // Log the incident wfa.action( action.core.log, { $id: Now.ID['log_incident'] }, { log_level: 'info', log_message: `Incident created: ${wfa.dataPill(params.trigger.current.short_description, 'string')}`, } ) // Conditional logic based on severity wfa.flowLogic.if( { $id: Now.ID['check_severity_high'], condition: `${wfa.dataPill(params.trigger.current.severity, 'string')}=1`, annotation: 'High severity (1)', }, () => { // Look up assignment group const assignmentGroup = wfa.action( action.core.lookUpRecord, { $id: Now.ID['lookup_assignment_group'] }, { table: 'sys_user_group', conditions: `sys_id=${wfa.dataPill(params.trigger.current.assignment_group, 'reference')}`, sort_type: 'sort_asc', if_multiple_records_are_found_action: 'use_first_record', } ) // Send notification wfa.action( action.core.sendNotification, { $id: Now.ID['send_urgent_email'] }, { table_name: 'incident', record: wfa.dataPill(params.trigger.current.sys_id, 'reference'), notification: 'high_severity_incident_notification', } ) } ) // Update record wfa.action( action.core.updateRecord, { $id: Now.ID['update_incident_state'] }, { table_name: 'incident', record: wfa.dataPill(params.trigger.current.sys_id, 'reference'), values: TemplateValue({ state: '2' }), } ) } ) ``` ## Subflow API Create reusable workflow components with typed inputs and outputs. ```typescript import { Subflow, action, wfa } from '@servicenow/sdk/automation' import { BooleanColumn, ReferenceColumn, StringColumn } from '@servicenow/sdk/core' export const newUserOnboardingSubflow = Subflow( { $id: Now.ID['new_user_onboarding_subflow'], name: 'New User Onboarding Subflow', description: 'Sends welcome notification, assigns laptop and desk', inputs: { user_sys_id: ReferenceColumn({ label: 'User', referenceTable: 'sys_user', mandatory: true, }), office_location: ReferenceColumn({ label: 'Office Location', referenceTable: 'cmn_location', mandatory: true, }), }, outputs: { laptop_assigned: BooleanColumn({ label: 'Laptop Assigned' }), desk_assigned: BooleanColumn({ label: 'Desk Assigned' }), laptop_number: StringColumn({ label: 'Laptop Asset Number', maxLength: 40 }), }, flowVariables: { laptop_found: BooleanColumn({ label: 'Laptop Found Flag', default: false }), }, }, (params) => { // Look up user const user = wfa.action( action.core.lookUpRecord, { $id: Now.ID['lookup_user'] }, { table: 'sys_user', conditions: `sys_id=${wfa.dataPill(params.inputs.user_sys_id, 'reference')}`, sort_type: 'sort_asc', if_multiple_records_are_found_action: 'use_first_record', } ) // Send welcome notification wfa.action( action.core.sendNotification, { $id: Now.ID['send_welcome'] }, { table_name: 'sys_user', record: wfa.dataPill(params.inputs.user_sys_id, 'reference'), notification: 'new_user_welcome_notification', } ) // Return outputs wfa.flowLogic.assignSubflowOutputs( { $id: Now.ID['assign_outputs'] }, params.outputs, { laptop_assigned: true, desk_assigned: true, laptop_number: wfa.dataPill(availableLaptop.Record.asset_tag, 'string'), } ) } ) ``` ## Service Catalog API Create catalog items with variables, UI policies, and client scripts for self-service portals. ```typescript import { CatalogItem, CatalogUiPolicy, CatalogClientScript, SelectBoxVariable, ReferenceVariable, DateVariable, YesNoVariable, MultiLineTextVariable, ContainerStartVariable, ContainerEndVariable, VariableSet, SingleLineTextVariable, EmailVariable, Role, } from '@servicenow/sdk/core' import { Flow, wfa, trigger } from '@servicenow/sdk/automation' // Create a reusable variable set export const contactInfoVariableSet = VariableSet({ $id: Now.ID['contact_info_variable_set'], title: 'Contact Information', description: 'Reusable set of contact-related variables', type: 'singleRow', layout: '2across', displayTitle: true, order: 100, variables: { contact_name: SingleLineTextVariable({ question: 'Full Name', order: 100, mandatory: true }), contact_email: EmailVariable({ question: 'Email Address', order: 200, mandatory: true }), contact_phone: SingleLineTextVariable({ question: 'Phone Number', order: 300, validateRegex: '/^\\+?[0-9\\-\\s]+$/', }), }, }) // Create a catalog item with fulfillment flow export const laptopRequest = CatalogItem({ $id: Now.ID['laptop_request_catalog_item'], name: 'Laptop Request', shortDescription: 'Request a new laptop for an employee', availability: 'both', order: 100, fulfillmentAutomationLevel: 'semiAutomated', cost: 1200, recurringFrequency: 'yearly', billable: true, variableSets: [{ variableSet: contactInfoVariableSet, order: 0 }], variables: { laptop_details_start: ContainerStartVariable({ question: 'Laptop Details', displayTitle: true, layout: '2across', order: 1000, }), laptop_type: SelectBoxVariable({ question: 'Laptop Type', order: 1100, mandatory: true, choices: { standard: { label: 'Standard (Business)' }, performance: { label: 'High Performance (Engineering)' }, ultrabook: { label: 'Ultrabook (Executive)' }, }, includeNone: false, }), needed_by: DateVariable({ question: 'Date Needed By', order: 1400 }), assigned_user: ReferenceVariable({ question: 'Assign To', order: 1500, referenceTable: 'sys_user', useReferenceQualifier: 'simple', referenceQualCondition: 'active=true', }), laptop_details_end: ContainerEndVariable({ order: 1600 }), include_dock: YesNoVariable({ question: 'Include Docking Station?', order: 2000, defaultValue: true }), justification: MultiLineTextVariable({ question: 'Business Justification', order: 2400, mandatory: true }), }, }) // UI Policy for catalog item export const showDockForStandard = CatalogUiPolicy({ $id: Now.ID['show_dock_policy'], shortDescription: 'Show docking station option only for standard laptops', catalogItem: laptopRequest, active: true, onLoad: true, reverseIfFalse: true, catalogCondition: `${laptopRequest.variables.laptop_type}=standard`, actions: [{ variableName: laptopRequest.variables.include_dock, visible: true }], }) // Client script for catalog item export const laptopOnLoadScript = CatalogClientScript({ $id: Now.ID['laptop_onload_script'], name: 'Set default needed-by date', catalogItem: laptopRequest, type: 'onLoad', active: true, uiType: 'all', script: ` function onLoad() { var today = new Date(); today.setDate(today.getDate() + 14); var defaultDate = today.toISOString().split('T')[0]; g_form.setValue('needed_by', defaultDate); }`, }) ``` ## UI Page API Create custom UI pages with HTML, client scripts, and server-side processing. ```typescript import { UiPage } from '@servicenow/sdk/core' // UI Page with external files UiPage({ $id: Now.ID['ui-page-sample'], category: 'general', endpoint: 'x_uipagesample_mypage.do', description: 'This is a sample UI Page created with the SDK', html: Now.include('./ui-page.html'), clientScript: Now.include('./ui-page.client-script.client.js'), processingScript: Now.include('./ui-page.processing-script.server.js'), }) // React-based UI Page import incidentPage from '../../client/index.html' UiPage({ $id: Now.ID['incident-manager-page'], endpoint: 'x_reactuisample_incident_manager.do', description: 'Incident Response Manager UI Page', category: 'general', html: incidentPage, direct: true, }) ``` ## Service Portal Widget API Create custom widgets for Service Portal with client/server scripts and Angular integration. ```typescript import { SPWidget } from '@servicenow/sdk/core' const CHARTJS = 'a7a8754347011200ba13a5554ee4905c' const SP_ELLIPSIS_TOOLTIP = '1d2b40e07323201081d3738234f6a714' SPWidget({ $id: Now.ID['sample-widget'], name: 'Sample Widget', id: 'sample-widget', clientScript: Now.include('sample-widget.client.js'), serverScript: Now.include('sample-widget.server.js'), htmlTemplate: Now.include('sample-widget.html'), customCss: Now.include('sample-widget.scss'), demoData: { data: { incidents: [99, 59, 80, 81, 56, 55, 40, 0, 5, 21, 11, 30], }, }, hasPreview: true, linkScript: `function link(scope, element, attrs, controller) {}`, dependencies: [CHARTJS], angularProviders: [SP_ELLIPSIS_TOOLTIP], }) ``` ## List API Configure list views for tables with custom columns and ordering. ```typescript import { Record, List } from '@servicenow/sdk/core' // Create a custom view const llama_task_view_1 = Record({ table: 'sys_ui_view', $id: Now.ID['llama_task_view_1'], data: { name: 'llama_task_view_1', title: 'llama_task_view_1', }, }) // Configure list columns for the view List({ $id: Now.ID['1'], table: 'cmdb_ci_server', view: llama_task_view_1, columns: [ { element: 'name', position: 0 }, { element: 'business_unit', position: 1 }, { element: 'vendor', position: 2 }, { element: 'cpu_type', position: 3 }, ], }) ``` ## Application Menu API Create navigation menus and modules for your application. ```typescript import { ApplicationMenu } from '@servicenow/sdk/core' import { activity_admin, appCategory } from './menu-category-roles.now' export const menu = ApplicationMenu({ $id: Now.ID['My App Menu'], title: 'My App Menu', hint: 'This is a hint', description: 'This is a description', category: appCategory, roles: [activity_admin], active: true, }) ``` ## Automated Test Framework (ATF) API Create automated tests for REST APIs and record operations. ```typescript import { Test } from '@servicenow/sdk/core' export default Test( { $id: 'a29a37229f703200ef4afa7dc67fcf9e', active: true, description: 'Create an incident and retrieve it via REST Table API.', name: 'Get Newly Created Resource via REST API Test', }, (atf) => { // Insert a test record atf.server.recordInsert({ $id: 'step1', assert: 'record_successfully_inserted', enforceSecurity: false, fieldValues: { short_description: 'REST Test Incident' }, table: 'incident', }) // Send REST request to retrieve the record atf.rest.sendRestRequest({ $id: 'step2', basicAuthentication: '', body: '', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, method: 'get', path: "/api/now/v2/table/incident/{{step['d42bb7229f703200ef4afa7dc67fcf46'].record_id}}", queryParameters: {}, }) // Assert response atf.rest.assertStatusCode({ $id: 'step3', operation: 'equals', statusCode: 200 }) atf.rest.assertResponseJSONPayloadIsValid({ $id: 'step4' }) atf.rest.assertJsonResponsePayloadElement({ $id: 'step5', elementName: '/result/short_description', elementValue: 'REST Test Incident', operation: 'equals', }) } ) ``` ## Summary The ServiceNow SDK enables developers to build complete applications using TypeScript and a fluent API pattern. The main use cases include creating custom tables with typed schemas, implementing business logic through business rules and script includes, building REST APIs for integrations, automating workflows with Flow Designer, creating self-service catalog items, and developing custom UI pages and Service Portal widgets. Each artifact is defined declaratively and compiled into deployable packages. Integration patterns focus on modular code organization where script logic is separated into external `.server.js` and `.client.js` files referenced via `Now.include()`. The SDK supports all major ServiceNow platform capabilities including ACLs for security, ATF for testing, and cross-scope module references. Projects are configured via `now.config.json` files that define the application scope, and dependencies are managed through standard npm tooling with pnpm workspaces for monorepo support.