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)
- No
GlideRecordin Client Scripts. (UseGlideAjaxonly). - No Synchronous Calls. (
getXMLWait,sj:, or synchronous REST). - No Hardcoded Sys IDs. (Use System Properties).
- No Queries inside Loops. (The "N+1" problem).
- No Direct Table Writes for Integrations. (Always use Import Sets).
The "Always" List
- Async First. If the user doesn't need to see the result right now, move it to an Async Business Rule or Flow.
- CSDM Alignment. Connect everything to an Application Service.
- Scoped Apps. Default to Scoped Apps for namespace protection (
x_company_app). - 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.
- Schema: Are we extending
Task? (Only if we need Approvals/SLAs). If high volume (>100k/year), use a custom table. - Indexing: What fields will be used in "Sort By" or "Group By"? Request indexes now.
- Integration: Are we writing to an Import Set Table first? (Mandatory for inbound data).
- 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
GlideRecordqueries insidewhileloops. - Count Strategy: Used
GlideAggregatefor counting, notgetRowCount(). - Update Safety: No
current.update()in Business Rules. - Client Performance: No
GlideRecordin Client Scripts. Usedg_scratchpadfor onLoad data. - Log Hygiene: No
gs.log. Used the standard_logDebugwrapper. - 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. |