ServiceNow Developer Onboarding Kit

ServiceNow Developer Onboarding Kit

No power in the universe can withhold from anyone anything he really deserves.
-Swami Vivekananda

The [Team Name] ServiceNow Developer Standard

Version: 2.0 | Status: Active
Target Audience: All Developers & Architects


1. The Manifesto: How We Build

We do not just "make it work." We build for scale, performance, and upgradeability. If you violate these core rules, your code will not pass Peer Review.

The "Never" List (Immediate Rejection)

  1. No GlideRecord in Client Scripts. (Use GlideAjax only).
  2. No Synchronous Calls. (getXMLWait, sj:, or synchronous REST).
  3. No Hardcoded Sys IDs. (Use System Properties).
  4. No Queries inside Loops. (The "N+1" problem).
  5. No Direct Table Writes for Integrations. (Always use Import Sets).

The "Always" List

  1. Async First. If the user doesn't need to see the result right now, move it to an Async Business Rule or Flow.
  2. CSDM Alignment. Connect everything to an Application Service.
  3. Scoped Apps. Default to Scoped Apps for namespace protection (x_company_app).
  4. Try/Catch. All JSON parsing and external calls must be wrapped in error handling.

2. Server-Side Standard: The "Handler" Pattern

We do not write ad-hoc logic in Business Rules. We call Script Includes. Use this Boilerplate for all new Script Includes.

📄 Copy-Paste Template: ServiceHandlerBase

var ServiceHandlerBase = Class.create();
ServiceHandlerBase.prototype = {

	initialize: function() {
		// Controlled by System Property: 'x_scope.debug.enabled'
		this.debug = gs.getProperty('x_scope.debug.enabled', 'false') == 'true';
		this.logPrefix = '[ServiceHandler]: ';
		this.startTime = new Date().getTime();
	},

	// STANDARD ENTRY POINT
	processLogic: function(payload) {
		var result = this._initResponse();

		try {
			this._logDebug('Starting processLogic with payload: ' + JSON.stringify(payload));

			// 1. Validate Input
			if (!payload) throw new Error('Missing payload parameter.');
			
			// 2. Normalize Data
			var dataObj = (typeof payload === 'string') ? JSON.parse(payload) : payload;
			
			// 3. Execute Business Logic
			// var recordID = this._internalFunction(dataObj);

			// 4. Success Response
			result.status = 'success';
			result.message = 'Operation complete';
			result.data = { processed_at: new GlideDateTime().getDisplayValue() };

		} catch (e) {
			result.status = 'error';
			result.message = e.message;
			this._logError('processLogic failed', e);
		} finally {
			this._logPerformance('processLogic');
		}

		return result;
	},

	// --- HELPERS ---
	
	_initResponse: function() {
		return { status: 'error', message: '', data: null };
	},

	_logDebug: function(msg) {
		if (this.debug) gs.info(this.logPrefix + msg);
	},

	_logError: function(msg, errorObj) {
		var details = (errorObj) ? ' | Msg: ' + errorObj.message + ' | Stack: ' + errorObj.stack : '';
		gs.error(this.logPrefix + 'ERROR: ' + msg + details);
	},

	_logPerformance: function(methodName) {
		if (this.debug) {
			var duration = new Date().getTime() - this.startTime;
			gs.info(this.logPrefix + 'PERF [' + methodName + ']: ' + duration + 'ms');
		}
	},

	type: 'ServiceHandlerBase'
};

3. Client-Side Standard: The "Resilient" Ajax

We prioritize User Experience. We never freeze the browser. Use this pattern for Client Scripts.

📄 Copy-Paste Template: GlideAjax

function onChange(control, oldValue, newValue, isLoading, isTemplate) {
	if (isLoading || newValue === '') return;

	// UX: Give feedback
	g_form.hideFieldMsg('your_field'); 
	g_form.showFieldMsg('your_field', 'Validating...', 'info');

	// 1. PREPARE PAYLOAD (One JSON object)
	var payload = {
		userID: g_user.userID,
		value: newValue,
		recordID: g_form.getUniqueValue()
	};

	// 2. CALL SERVER
	var ga = new GlideAjax('x_scope.ServiceHandlerBase'); 
	ga.addParam('sysparm_name', 'processLogic');
	ga.addParam('sysparm_data', JSON.stringify(payload)); 

	// 3. HANDLE RESPONSE (Using getXMLAnswer)
	ga.getXMLAnswer(function(answer) {
		g_form.hideFieldMsg('your_field');

		if (!answer) {
			g_form.addErrorMessage('Connection Error: No response from server.');
			return;
		}

		try {
			var response = JSON.parse(answer);

			if (response.status === 'success') {
				// SUCCESS LOGIC
				// g_form.setValue('target', response.data.val);
			} else {
				// LOGIC ERROR (e.g. Validation failed)
				g_form.showFieldMsg('your_field', response.message, 'error');
			}
		} catch (e) {
			// CRITICAL ERROR (HTML response / 500 Error)
			console.error('JSON Parse Error:', e);
			g_form.addErrorMessage('System Error. Please contact support.');
		}
	});
}

4. The Design Process (TDD)

Before you write code, you must answer these questions in your Ticket/Story.

  1. Schema: Are we extending Task? (Only if we need Approvals/SLAs). If high volume (>100k/year), use a custom table.
  2. Indexing: What fields will be used in "Sort By" or "Group By"? Request indexes now.
  3. Integration: Are we writing to an Import Set Table first? (Mandatory for inbound data).
  4. Namespace: Are we in the correct Scope?

5. The "Gatekeeper" Checklist (Code Review)

Your code will be rejected if these are not met.

  • Loop Safety: No GlideRecord queries inside while loops.
  • Count Strategy: Used GlideAggregate for counting, not getRowCount().
  • Update Safety: No current.update() in Business Rules.
  • Client Performance: No GlideRecord in Client Scripts. Used g_scratchpad for onLoad data.
  • Log Hygiene: No gs.log. Used the standard _logDebug wrapper.
  • Credential Safety: No passwords in code. Used Credential Aliases.

6. Expert Toolkit

Save these for emergency debugging.

Tool Usage Purpose
Cancel Transaction cancel_my_transaction.do The "Panic Button" when a script freezes your browser.
Node Stats stats.do Check memory/semaphore usage.
Filter Trick Table.FILTER Opens a table without loading data (prevents crashing on massive tables).
Cluster Msg GlideClusterMessage Used to flush cache across all nodes.
Row Lock setWorkflow(false) Warning: Disables Activity Stream and sys_updated_on. Use with extreme caution.