client-scripts
Skill41 starsChanged 6 days agoApache-2.0View on GitHub
Complete guide to ServiceNow client script development including onLoad, onChange, onSubmit, onCellEdit types, g_form API, GlideAjax server calls, and performance optimization
We split this skill into the rules it gives an agent
10 rules. About 0 are common, 10 are distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈10
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 40
When to use: When you need to execute JavaScript in the browser to manipulate form behavior, validate data, or provide dynamic user …
- Distinctive: line 1129
Minimize Server Calls: Use g_scratchpad, batch GlideAjax, cache results
- Distinctive: line 1130
Handle isLoading: Always check in onChange to avoid initialization issues
- Distinctive: line 1133
Avoid DOM Access: Use g_form API for cross-platform compatibility
- Distinctive: line 1135
Console Logging: Use for development, remove in production
- Distinctive: line 1136
Mobile Testing: Test scripts with ui_type=10 on actual devices
- Distinctive: line 1143
Symptom: No console output, form unchanged
- Distinctive: line 1159
Check active=true, verify condition, check ui_type matches your environment.
- Distinctive: line 1204
Verify client_callable=true.
- Distinctive: line 1217
Use g_scratchpad for server data
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Client Scripts
Overview
This skill covers comprehensive client script development in ServiceNow:
- Client script types: onLoad, onChange, onSubmit, onCellEdit
- The g_form API for form manipulation
- g_user and g_scratchpad objects for session context
- GlideAjax for asynchronous server calls
- Performance optimization and best practices
- Mobile/Service Portal considerations
- Debugging techniques and common patterns
When to use: When you need to execute JavaScript in the browser to manipulate form behavior, validate data, or provide dynamic user interactions.
Who should use this: Developers building custom form behaviors, validations, and user experience enhancements.
Prerequisites
- Roles:
client_script_adminoradmin - Access: sys_script_client, sys_ui_script tables
- Knowledge: JavaScript fundamentals, ServiceNow form architecture
- Related Skills:
catalog/ui-policiesfor simpler show/hide/mandatory logic
Understanding Client Scripts
Script Type Comparison
| Type | Trigger | Use Case | Performance Impact |
| onLoad | Form loads | Initialize fields, set defaults | Medium |
| onChange | Field value changes | Field dependencies, cascading logic | Low-Medium |
| onSubmit | Form submission | Validation, confirmation | Low |
| onCellEdit | List cell edited | List editing validation | Low |
Client Script vs UI Policy
┌─────────────────────────────────────────────────────────────┐
│ Decision Matrix │
├─────────────────────────────────────────────────────────────┤
│ Need to show/hide/mandatory fields? │
│ YES → Use UI Policy (no code, easier maintenance) │
│ │
│ Need to set field values or complex logic? │
│ YES → Use Client Script │
│ │
│ Need server-side data? │
│ YES → Use Client Script with GlideAjax │
│ │
│ Need to prevent form submission? │
│ YES → Use Client Script (onSubmit) │
│ │
│ Simple field validation? │
│ Dictionary validation → UI Policy → Client Script │
└─────────────────────────────────────────────────────────────┘Execution Order
Form Load Sequence:
1. UI Policies (on load = true) evaluate
2. onLoad Client Scripts execute (by order)
3. Default values applied
4. Field-level ACLs applied
Field Change Sequence:
1. onChange Client Script for field executes
2. UI Policies with that field in condition re-evaluate
3. Related onchange handlers fire
Form Submit Sequence:
1. onSubmit Client Scripts execute (by order)
2. If all return true, form submits
3. Server-side business rules fireProcedure
Phase 1: Creating Client Scripts
Step 1.1: Create an onLoad Script
Basic onLoad Structure:
Tool: SN-Create-Record
Parameters:
table_name: sys_script_client
data:
name: "Initialize Request Form"
table: incident
type: onLoad
script: |
function onLoad() {
// Set default values
g_form.setValue('contact_type', 'email');
// Hide fields for new records
if (g_form.isNewRecord()) {
g_form.setDisplay('resolution_notes', false);
g_form.setDisplay('resolved_by', false);
}
// Show informational message
g_form.addInfoMessage('Please provide detailed information for faster resolution.');
}
active: true
order: 100
ui_type: 0ui_type Values:
| Value | Meaning |
| 0 | Desktop |
| 1 | Mobile/Service Portal |
| 10 | Both Desktop and Mobile |
Step 1.2: Create an onChange Script
onChange with Field Dependency:
Tool: SN-Create-Record
Parameters:
table_name: sys_script_client
data:
name: "Category Sets Subcategory Options"
table: incident
type: onChange
field_name: category
script: |
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
// Skip if loading form or using template
if (isLoading || isTemplate) {
return;
}
// Clear dependent field when parent changes
g_form.clearValue('subcategory');
// Set subcategory based on category
if (newValue == 'hardware') {
g_form.setValue('assignment_group', 'Hardware Support');
} else if (newValue == 'software') {
g_form.setValue('assignment_group', 'Software Support');
}
}
active: true
order: 100
ui_type: 0onChange Parameters Explained:
| Parameter | Description |
| control | The form element (rarely used) |
| oldValue | Previous field value |
| newValue | New field value (current) |
| isLoading | true if form is loading |
| isTemplate | true if using a template |
Step 1.3: Create an onSubmit Script
Validation onSubmit:
Tool: SN-Create-Record
Parameters:
table_name: sys_script_client
data:
name: "Validate Required Fields Before Submit"
table: incident
type: onSubmit
script: |
function onSubmit() {
// Get field values
var shortDesc = g_form.getValue('short_description');
var category = g_form.getValue('category');
var priority = g_form.getValue('priority');
// Validate short description length
if (shortDesc.length < 10) {
g_form.addErrorMessage('Short description must be at least 10 characters.');
g_form.flash('short_description', '#FF0000', 0);
return false; // Prevent submission
}
// Validate P1 requires assignment group
if (priority == '1' && !g_form.getValue('assignment_group')) {
g_form.addErrorMessage('P1 incidents require an assignment group.');
return false;
}
// Confirm high priority submission
if (priority == '1' || priority == '2') {
var confirmed = confirm('You are submitting a high priority incident. Continue?');
if (!confirmed) {
return false;
}
}
return true; // Allow submission
}
active: true
order: 100
ui_type: 0Step 1.4: Create an onCellEdit Script
List Editing Validation:
Tool: SN-Create-Record
Parameters:
table_name: sys_script_client
data:
name: "Validate Priority Change in List"
table: incident
type: onCellEdit
script: |
function onCellEdit(sysIDs, table, oldValues, newValue, callback) {
// sysIDs: array of selected record sys_ids
// oldValues: array of previous values
// newValue: the new value being set
// callback: must be called to complete the edit
var priority = g_form.getValue('priority');
// Prevent bulk P1 changes
if (newValue == '1' && sysIDs.length > 1) {
g_form.addErrorMessage('Cannot bulk change to P1. Edit records individually.');
callback(false); // Cancel edit
return;
}
// Confirm P1 assignment
if (newValue == '1') {
var confirmed = confirm('Setting priority to Critical. This will escalate the incident. Continue?');
callback(confirmed);
return;
}
callback(true); // Allow edit
}
active: true
order: 100Phase 2: The g_form API
Step 2.1: Getting and Setting Values
Essential g_form Methods:
// Get field values
var value = g_form.getValue('field_name'); // Internal value
var display = g_form.getDisplayValue('field_name'); // Display value
var reference = g_form.getReference('assigned_to'); // Reference object (deprecated - use GlideAjax)
// Set field values
g_form.setValue('field_name', 'value'); // Set value
g_form.setValue('assigned_to', sysId, displayValue); // Set reference with display
g_form.clearValue('field_name'); // Clear to empty
// Check field states
var isEmpty = g_form.getValue('field_name') == ''; // Check empty
var isNewRecord = g_form.isNewRecord(); // New vs existingReference Field Handling:
// DEPRECATED - Makes synchronous server call (performance issue!)
var user = g_form.getReference('assigned_to');
var email = user.email;
// BETTER - Use callback (still not ideal)
g_form.getReference('assigned_to', function(ref) {
var email = ref.email;
// Continue processing
});
// BEST - Use GlideAjax (see Phase 4)Step 2.2: Visibility and State Control
// Visibility
g_form.setDisplay('field_name', true); // Show field (affects row)
g_form.setVisible('field_name', true); // Show field (preserves space)
g_form.hideFieldMsg('field_name'); // Hide field message
// State control
g_form.setMandatory('field_name', true); // Make required
g_form.setReadOnly('field_name', true); // Make read-only
g_form.setDisabled('field_name', true); // Disable (grayed out)
// Labels
g_form.setLabelOf('field_name', 'New Label'); // Change label text
// Options (choice fields)
g_form.clearOptions('priority'); // Remove all options
g_form.addOption('priority', '1', 'Critical', 0); // Add option (value, label, index)
g_form.removeOption('priority', '5'); // Remove specific optionStep 2.3: Messages and Highlighting
// Form-level messages
g_form.addInfoMessage('Information message');
g_form.addWarningMessage('Warning message');
g_form.addErrorMessage('Error message');
g_form.clearMessages(); // Clear all messages
// Field-level messages
g_form.showFieldMsg('field_name', 'Message text', 'info'); // info, warning, error
g_form.hideFieldMsg('field_name'); // Clear field message
g_form.hideAllFieldMsgs(); // Clear all field messages
// Visual highlighting
g_form.flash('field_name', '#FF0000', 0); // Flash red (color, count; 0=once)Step 2.4: Section and Related List Control
// Sections (tabs)
g_form.setSectionDisplay('section_name', true); // Show/hide section
g_form.isSectionVisible('section_name'); // Check visibility
g_form.activateTab('section_name'); // Switch to tab
// Related lists (limited support)
// Use UI Actions or GlideAjax for related list operationsPhase 3: g_user and g_scratchpad Objects
Step 3.1: The g_user Object
The g_user object provides information about the currently logged-in user:
// User identification
var userSysId = g_user.userID; // User sys_id
var userName = g_user.userName; // Username (login name)
var firstName = g_user.firstName; // First name
var lastName = g_user.lastName; // Last name
var fullName = g_user.getFullName(); // Full display name
// Role checks
var isAdmin = g_user.hasRole('admin'); // Check single role
var isItil = g_user.hasRoleExactly('itil'); // Exact role match
var hasAnyRole = g_user.hasRoles(); // Has any role
// Client data (set in business rules)
var customData = g_user.getClientData('custom_key');
// Preferences
var pref = g_user.getPreference('preference_name');Setting Client Data from Server (Business Rule):
// Server-side (business rule, before query/display)
gs.getSession().putClientData('manager_email', current.caller_id.manager.email);
// Client-side (client script)
var managerEmail = g_user.getClientData('manager_email');Step 3.2: The g_scratchpad Object
g_scratchpad passes data from server to client during form load:
Server-Side (Display Business Rule):
// Type: display, When: before
// Set scratchpad values for client access
g_scratchpad.isVip = current.caller_id.vip == true;
g_scratchpad.callerCompany = current.caller_id.company.name.toString();
g_scratchpad.maxPriority = gs.getProperty('incident.max_priority', '3');Client-Side (onLoad Script):
function onLoad() {
// Access scratchpad data (no server call needed!)
if (g_scratchpad.isVip) {
g_form.addInfoMessage('VIP Caller - Handle with priority');
g_form.setValue('priority', '2');
}
// Use server-side property value
var maxPriority = g_scratchpad.maxPriority;
// Remove low priority options for VIP
if (g_scratchpad.isVip) {
g_form.removeOption('priority', '5');
g_form.removeOption('priority', '4');
}
}Phase 4: GlideAjax for Server Calls
Step 4.1: Create a Script Include
First, create a client-callable Script Include:
Tool: SN-Create-Record
Parameters:
table_name: sys_script_include
data:
name: "IncidentAjaxUtils"
api_name: IncidentAjaxUtils
client_callable: true
script: |
var IncidentAjaxUtils = Class.create();
IncidentAjaxUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {
// Get user details by sys_id
getUserDetails: function() {
var userId = this.getParameter('sysparm_user_id');
var result = {};
var user = new GlideRecord('sys_user');
if (user.get(userId)) {
result.name = user.name.toString();
result.email = user.email.toString();
result.phone = user.phone.toString();
result.department = user.department.getDisplayValue();
result.manager = user.manager.getDisplayValue();
result.vip = user.vip == true;
}
return JSON.stringify(result);
},
// Validate assignment group can handle priority
validateAssignment: function() {
var groupId = this.getParameter('sysparm_group_id');
var priority = this.getParameter('sysparm_priority');
var result = { valid: true, message: '' };
var group = new GlideRecord('sys_user_group');
if (group.get(groupId)) {
// Check if group handles this priority
var canHandleP1 = group.u_handles_critical == true;
if (priority == '1' && !canHandleP1) {
result.valid = false;
result.message = group.name + ' does not handle Critical incidents. Please select a Critical-capable group.';
}
}
return JSON.stringify(result);
},
// Get related incidents count
getRelatedIncidentCount: function() {
var ciId = this.getParameter('sysparm_ci_id');
var count = 0;
if (ciId) {
var ga = new GlideAggregate('incident');
ga.addQuery('cmdb_ci', ciId);
ga.addQuery('active', true);
ga.addAggregate('COUNT');
ga.query();
if (ga.next()) {
count = ga.getAggregate('COUNT');
}
}
return count.toString();
},
type: 'IncidentAjaxUtils'
});
access: public
active: trueStep 4.2: Call GlideAjax from Client Script
Basic GlideAjax Pattern:
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || isTemplate || !newValue) {
return;
}
// Create GlideAjax call
var ga = new GlideAjax('IncidentAjaxUtils');
ga.addParam('sysparm_name', 'getUserDetails'); // Method name
ga.addParam('sysparm_user_id', newValue); // Custom parameter
// Make asynchronous call
ga.getXMLAnswer(function(response) {
// Parse JSON response
var user = JSON.parse(response);
if (user.name) {
// Update form with retrieved data
g_form.setValue('u_caller_email', user.email);
g_form.setValue('u_caller_phone', user.phone);
// VIP handling
if (user.vip) {
g_form.addInfoMessage('VIP Caller: ' + user.name);
g_form.setValue('priority', '2');
}
}
});
}Validation with GlideAjax:
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || isTemplate || !newValue) {
return;
}
var priority = g_form.getValue('priority');
var ga = new GlideAjax('IncidentAjaxUtils');
ga.addParam('sysparm_name', 'validateAssignment');
ga.addParam('sysparm_group_id', newValue);
ga.addParam('sysparm_priority', priority);
ga.getXMLAnswer(function(response) {
var result = JSON.parse(response);
if (!result.valid) {
g_form.showFieldMsg('assignment_group', result.message, 'error');
g_form.setValue('assignment_group', '');
} else {
g_form.hideFieldMsg('assignment_group');
}
});
}Step 4.3: GlideAjax with getXML (Full Response)
For more control over the response:
function onLoad() {
var ciId = g_form.getValue('cmdb_ci');
if (!ciId) return;
var ga = new GlideAjax('IncidentAjaxUtils');
ga.addParam('sysparm_name', 'getRelatedIncidentCount');
ga.addParam('sysparm_ci_id', ciId);
ga.getXML(function(response) {
// Get the answer element
var answer = response.responseXML.documentElement.getAttribute('answer');
var count = parseInt(answer) || 0;
if (count > 5) {
g_form.addWarningMessage('This CI has ' + count + ' active incidents. Consider checking for related problems.');
}
});
}Phase 5: Performance Best Practices
Step 5.1: Minimize Server Calls
// BAD - Multiple synchronous calls
function onLoad() {
var caller = g_form.getReference('caller_id'); // Server call 1
var assignee = g_form.getReference('assigned_to'); // Server call 2
var group = g_form.getReference('assignment_group'); // Server call 3
// Form takes 3+ seconds to load!
}
// BETTER - Use g_scratchpad (set in Display Business Rule)
function onLoad() {
// Data already available - no server calls!
var callerVip = g_scratchpad.callerVip;
var assigneeEmail = g_scratchpad.assigneeEmail;
}
// BEST - Single GlideAjax call returning all needed data
function onLoad() {
var ga = new GlideAjax('FormDataUtils');
ga.addParam('sysparm_name', 'getFormContext');
ga.addParam('sysparm_incident_id', g_form.getUniqueValue());
ga.getXMLAnswer(function(response) {
var data = JSON.parse(response);
// All data in one call
processFormData(data);
});
}Step 5.2: Use Conditions Wisely
// BAD - Script runs for every incident
function onLoad() {
if (g_form.getValue('priority') == '1') {
// Logic only needed for P1
}
}
// BETTER - Use script condition in client script record
// Condition: priority=1
function onLoad() {
// Only runs when priority is 1
// No condition check needed in script
}Set Condition in Client Script Record:
Tool: SN-Create-Record
Parameters:
table_name: sys_script_client
data:
name: "P1 Special Handling"
table: incident
type: onLoad
condition: "priority=1"
script: |
function onLoad() {
// Only executes when condition matches
g_form.addInfoMessage('Critical incident - expedited handling required');
}
active: trueStep 5.3: Debounce Rapid Changes
// BAD - Ajax call on every keystroke
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading) return;
// Fires on every character typed!
var ga = new GlideAjax('SearchUtils');
ga.addParam('sysparm_name', 'search');
ga.addParam('sysparm_query', newValue);
ga.getXMLAnswer(handleResults);
}
// BETTER - Debounce rapid changes
var searchTimeout;
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading) return;
// Clear previous timeout
if (searchTimeout) {
clearTimeout(searchTimeout);
}
// Wait 500ms after last change before searching
searchTimeout = setTimeout(function() {
var ga = new GlideAjax('SearchUtils');
ga.addParam('sysparm_name', 'search');
ga.addParam('sysparm_query', newValue);
ga.getXMLAnswer(handleResults);
}, 500);
}Step 5.4: Cache Reference Data
// Store lookup results to avoid repeated calls
var categoryAssignments = {};
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || !newValue) return;
// Check cache first
if (categoryAssignments[newValue]) {
g_form.setValue('assignment_group', categoryAssignments[newValue]);
return;
}
// Fetch and cache
var ga = new GlideAjax('CategoryUtils');
ga.addParam('sysparm_name', 'getAssignmentGroup');
ga.addParam('sysparm_category', newValue);
ga.getXMLAnswer(function(response) {
var groupId = response;
categoryAssignments[newValue] = groupId; // Cache for next time
g_form.setValue('assignment_group', groupId);
});
}Phase 6: Mobile and Service Portal Considerations
Step 6.1: Mobile-Compatible Scripts
Tool: SN-Create-Record
Parameters:
table_name: sys_script_client
data:
name: "Mobile-Compatible Validation"
table: incident
type: onSubmit
ui_type: 10
script: |
function onSubmit() {
// These g_form methods work on mobile
var shortDesc = g_form.getValue('short_description');
if (!shortDesc || shortDesc.length < 5) {
// Use g_form messages (works on mobile)
g_form.addErrorMessage('Please provide a description');
return false;
}
// AVOID: alert(), confirm(), prompt() - don't work on mobile
// AVOID: document.getElementById() - DOM may differ
// AVOID: jQuery selectors - not available
return true;
}
active: trueMobile-Compatible g_form Methods:
| Method | Desktop | Mobile | Service Portal |
| getValue() | Yes | Yes | Yes |
| setValue() | Yes | Yes | Yes |
| setMandatory() | Yes | Yes | Yes |
| setDisplay() | Yes | Yes | Yes |
| setReadOnly() | Yes | Yes | Yes |
| addErrorMessage() | Yes | Yes | Yes |
| showFieldMsg() | Yes | Yes | Yes |
| getReference() | Yes | No | Limited |
| flash() | Yes | No | No |
| activateTab() | Yes | No | No |
Step 6.2: Service Portal UI Scripts
For Service Portal-specific client logic, use UI Scripts:
Tool: SN-Create-Record
Parameters:
table_name: sys_ui_script
data:
name: "SP Incident Form Handler"
script: |
// Service Portal UI Script
// Runs in the Service Portal context
function spIncidentFormInit() {
// Use $scope for Angular integration
// Access widget data
console.log('SP Incident form initialized');
}
// Self-executing for immediate availability
if (typeof angular !== 'undefined') {
angular.element(document).ready(function() {
spIncidentFormInit();
});
}
active: true
global: false
ui_type: 10Phase 7: Debugging Client Scripts
Step 7.1: Console Logging
function onLoad() {
// Development logging (remove in production)
console.log('=== Client Script: Initialize Form ===');
console.log('Record sys_id:', g_form.getUniqueValue());
console.log('Is new record:', g_form.isNewRecord());
console.log('Current user:', g_user.userName);
// Log scratchpad contents
console.log('Scratchpad:', JSON.stringify(g_scratchpad));
// Performance timing
console.time('formSetup');
// ... form setup logic ...
console.timeEnd('formSetup');
}Step 7.2: JavaScript Debugger
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
// Add breakpoint for debugging
debugger; // Browser will pause here when DevTools open
if (isLoading) return;
// Step through logic in browser DevTools
var priority = g_form.getValue('priority');
var category = g_form.getValue('category');
// Inspect variables in DevTools console
}Step 7.3: Error Handling
function onLoad() {
try {
// Main logic
initializeForm();
setupFieldDependencies();
loadReferenceData();
} catch (error) {
// Log error details
console.error('Client Script Error:', error.message);
console.error('Stack:', error.stack);
// Notify user gracefully
g_form.addErrorMessage('Form initialization error. Please refresh the page.');
}
}
function initializeForm() {
// Potentially error-prone code
var data = JSON.parse(g_scratchpad.formData);
// ...
}Step 7.4: Query Client Scripts for Debugging
Find all client scripts for a table:
Tool: SN-Query-Table
Parameters:
table_name: sys_script_client
query: table=incident^active=true
fields: sys_id,name,type,field_name,order,condition,ui_type
limit: 50Check script execution order:
Tool: SN-Query-Table
Parameters:
table_name: sys_script_client
query: table=incident^active=true^type=onLoad
fields: name,order,condition
order_by: order
limit: 50Phase 8: Common Patterns
Pattern 1: Cascading Field Dependencies
// Category -> Subcategory -> Assignment Group cascade
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || isTemplate) return;
// Clear downstream fields
g_form.clearValue('subcategory');
g_form.clearValue('assignment_group');
if (!newValue) return;
// Fetch valid subcategories for this category
var ga = new GlideAjax('CategoryUtils');
ga.addParam('sysparm_name', 'getSubcategories');
ga.addParam('sysparm_category', newValue);
ga.getXMLAnswer(function(response) {
var subcategories = JSON.parse(response);
// Clear and repopulate options
g_form.clearOptions('subcategory');
g_form.addOption('subcategory', '', '-- Select --');
subcategories.forEach(function(sub) {
g_form.addOption('subcategory', sub.value, sub.label);
});
});
}Pattern 2: Conditional Mandatory Fields
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading) return;
// State = Resolved (6) requires resolution notes
var isResolved = (newValue == '6');
g_form.setMandatory('resolution_code', isResolved);
g_form.setMandatory('resolution_notes', isResolved);
g_form.setDisplay('resolution_code', isResolved);
g_form.setDisplay('resolution_notes', isResolved);
if (isResolved) {
g_form.addInfoMessage('Please provide resolution details.');
}
}Pattern 3: Form Validation with Multiple Checks
function onSubmit() {
var errors = [];
// Check 1: Short description length
var shortDesc = g_form.getValue('short_description');
if (shortDesc.length < 10) {
errors.push('Short description must be at least 10 characters');
g_form.showFieldMsg('short_description', 'Too short', 'error');
}
// Check 2: High priority requires assignment
var priority = g_form.getValue('priority');
var assignee = g_form.getValue('assigned_to');
var group = g_form.getValue('assignment_group');
if ((priority == '1' || priority == '2') && !assignee && !group) {
errors.push('High priority incidents must be assigned');
}
// Check 3: Configuration item recommended
var ci = g_form.getValue('cmdb_ci');
if (!ci) {
// Warning only, don't block
var proceed = confirm('No Configuration Item specified. Continue anyway?');
if (!proceed) {
return false;
}
}
// Display all errors
if (errors.length > 0) {
g_form.addErrorMessage('Please fix the following issues:\n' + errors.join('\n'));
return false;
}
return true;
}Pattern 4: Auto-populate from Reference
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || isTemplate || !newValue) {
return;
}
// Get caller details via GlideAjax
var ga = new GlideAjax('UserUtils');
ga.addParam('sysparm_name', 'getUserInfo');
ga.addParam('sysparm_user_id', newValue);
ga.getXMLAnswer(function(response) {
var user = JSON.parse(response);
// Auto-populate fields
g_form.setValue('location', user.location);
g_form.setValue('u_department', user.department);
g_form.setValue('u_phone', user.phone);
// VIP handling
if (user.vip) {
g_form.setValue('priority', '2');
g_form.addInfoMessage('VIP Caller - Priority elevated');
}
});
}Phase 9: Anti-Patterns to Avoid
Anti-Pattern 1: Synchronous Server Calls
// BAD - Blocks the UI
function onLoad() {
var user = g_form.getReference('caller_id'); // Synchronous!
var manager = g_form.getReference('assigned_to'); // More blocking!
// User experiences frozen form
}
// GOOD - Asynchronous with callback
function onLoad() {
var callerId = g_form.getValue('caller_id');
if (!callerId) return;
var ga = new GlideAjax('UserUtils');
ga.addParam('sysparm_name', 'getUserInfo');
ga.addParam('sysparm_user_id', callerId);
ga.getXMLAnswer(processUser); // Non-blocking
}Anti-Pattern 2: DOM Manipulation
// BAD - Direct DOM access
function onLoad() {
document.getElementById('incident.short_description').style.backgroundColor = 'yellow';
jQuery('#incident\\.priority').hide();
}
// GOOD - Use g_form API
function onLoad() {
g_form.flash('short_description', '#FFFF00', 3);
g_form.setDisplay('priority', false);
}Anti-Pattern 3: Hardcoded sys_ids
// BAD - Hardcoded values
function onChange(control, oldValue, newValue, isLoading) {
if (newValue == 'hardware') {
g_form.setValue('assignment_group', 'a715cd759f2002002920bde8132e7018'); // Magic string!
}
}
// GOOD - Use GlideAjax lookup or sys_properties
function onChange(control, oldValue, newValue, isLoading) {
if (isLoading || !newValue) return;
var ga = new GlideAjax('AssignmentUtils');
ga.addParam('sysparm_name', 'getGroupByCategory');
ga.addParam('sysparm_category', newValue);
ga.getXMLAnswer(function(groupId) {
g_form.setValue('assignment_group', groupId);
});
}Anti-Pattern 4: Heavy onLoad Processing
// BAD - Too much in onLoad
function onLoad() {
// 5 GlideAjax calls
loadCallerInfo();
loadCIDetails();
loadRelatedIncidents();
loadSLAStatus();
loadApprovalHistory();
// Form takes forever to load!
}
// GOOD - Use g_scratchpad for essential data, lazy load the rest
function onLoad() {
// Essential data from g_scratchpad (set in display business rule)
if (g_scratchpad.callerVip) {
g_form.addInfoMessage('VIP Caller');
}
// Load additional data only when needed
// e.g., when user clicks a tab or expands a section
}Tool Usage Summary
| Operation | MCP Tool | Table |
| Create Client Script | SN-Create-Record | sys_script_client |
| Update Client Script | SN-Update-Record | sys_script_client |
| Query Client Scripts | SN-Query-Table | sys_script_client |
| Create Script Include | SN-Create-Record | sys_script_include |
| Create UI Script | SN-Create-Record | sys_ui_script |
| Get Schema | SN-Get-Table-Schema | Any table |
| Local edit cycle | SN-Get-Record + SN-Update-Record | Pull, freshness check, edit, push |
Best Practices
- Use UI Policies First: For simple show/hide/mandatory, avoid code
- Minimize Server Calls: Use g_scratchpad, batch GlideAjax, cache results
- Handle isLoading: Always check in onChange to avoid initialization issues
- Return Values: onSubmit must return true/false explicitly
- Use Script Conditions: Filter execution at the platform level
- Avoid DOM Access: Use g_form API for cross-platform compatibility
- Error Handling: Wrap risky code in try-catch blocks
- Console Logging: Use for development, remove in production
- Mobile Testing: Test scripts with ui_type=10 on actual devices
- Version Control: Pull
script,sys_updated_on, andsys_mod_countwithSN-Get-Record; edit locally; freshness-check again; then push the reviewed field withSN-Update-Record
Troubleshooting
Script Not Executing
Symptom: No console output, form unchanged Causes:
- Script inactive
- Condition not matched
- ui_type mismatch (desktop vs mobile)
- JavaScript syntax error
Solution:
Tool: SN-Query-Table
Parameters:
table_name: sys_script_client
query: table=incident^name=*my script*
fields: name,active,condition,ui_typeCheck active=true, verify condition, check ui_type matches your environment.
onChange Not Firing
Symptom: Changing field value doesn't trigger script Causes:
- field_name doesn't match actual field
- isLoading/isTemplate not handled
- Script error before reaching logic
Solution:
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
console.log('onChange fired:', {
field: 'field_name',
old: oldValue,
new: newValue,
loading: isLoading
});
if (isLoading || isTemplate) {
console.log('Skipping - form loading');
return;
}
// ...
}GlideAjax Returns Empty
Symptom: Response is empty or undefined Causes:
- Script include not client_callable
- Method name misspelled
- Server-side error in script include
Solution:
Tool: SN-Query-Table
Parameters:
table_name: sys_script_include
query: name=MyAjaxUtil
fields: name,client_callable,activeVerify client_callable=true. Check System Logs for server errors.
Form Loads Slowly
Symptom: Long delay before form is interactive Causes:
- Synchronous getReference calls
- Multiple GlideAjax calls in onLoad
- Heavy DOM manipulation
Solution:
- Replace getReference with GlideAjax
- Combine multiple Ajax calls into one
- Use g_scratchpad for server data
- Profile with browser DevTools Performance tab
Related Skills
catalog/ui-policies- No-code show/hide/mandatoryadmin/script-execution- Server-side script patternsdevelopment/business-rules- Server-side form processingdevelopment/script-includes- Reusable server functionsadmin/script-sync- Local development workflow
References
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- Mlu-ctrl-alt-design/claude-skills★ 1same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository184 other files it gives its agents.
- happy-platform-skillsSkill
- application-scopeSkill
- batch-operationsSkill
- configuration-validationSkill
- deployment-workflowSkill
- generic-crud-operationsSkill
- instance-managementSkill
- schema-discoverySkill
- script-executionSkill
- script-syncSkill
- smart-assessmentSkill
- task-analysisSkill
- task-summarizationSkill
- update-set-managementSkill
- user-provisioningSkill
- workflow-creationSkill
- workspace-insightsSkill
- approval-summarizationSkill
- approval-workflowsSkill
- catalog-builder-aiSkill
- catalog-item-generationSkill
- item-creationSkill
- multi-turn-orderingSkill
- request-fulfillmentSkill
- ui-policiesSkill
- variable-managementSkill
- ci-discoverySkill
- cmdb-search-analysisSkill
- data-qualitySkill
- impact-analysisSkill
- relationship-mappingSkill
- service-graph-diagnosisSkill
- activity-responseSkill
- csm-case-summarizationSkill
- chat-recommendationSkill
- email-recommendationSkill
- csm-kb-generationSkill
- resolution-notesSkill
- csm-sentiment-analysisSkill
- csm-sidebar-summarizationSkill
- csm-suggested-stepsSkill
- trending-topicsSkill
- automated-testingSkill
- business-rulesSkill
- code-assistSkill
- code-reviewSkill
- data-importSkill
- debugging-techniquesSkill
- fluent-sdkSkill
- glide-api-referenceSkill
- mcp-server-installationSkill
- mcp-serverSkill
- notificationsSkill
- performance-optimizationSkill
- scheduled-jobsSkill
- scripted-rest-apisSkill
- script-includesSkill
- servicenow-docs-mcpSkill
- test-generationSkill
- ui-actionsSkill
- The first 60 shown.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

