10 Real-Time Examples of ServiceNow Client Scripts: A Complete Guide

ServiceNow Client Scripts are JavaScript code snippets that run directly in the user’s browser, enabling dynamic form behavior, instant validation, and interactive user experiences—all without requiring a round trip to the server.

This guide walks you through the fundamentals, the four types of Client Scripts, and ten practical, real-world examples you can implement immediately.


What Are ServiceNow Client Scripts?

Client Scripts execute on the client-side (in the user’s browser) to dynamically control forms and fields. Unlike server-side Business Rules, they provide immediate feedback to users, resulting in a more responsive experience.

Key Characteristics

  • Execution Environment: User’s browser
  • Primary API: GlideForm (g_form)
  • Purpose: Form manipulation, field validation, user interaction
  • Performance Impact: Minimal server load
  • User Experience: Instantaneous feedback without page reloads

When to Use Client Scripts

  • Validating form data before submission
  • Showing or hiding fields based on selections
  • Setting dynamic default values
  • Providing real-time feedback
  • Controlling mandatory field requirements

The 4 Types of Client Scripts

1. onLoad Scripts

Runs: When a form loads and fields are populated.

Best for:

  • Setting default values
  • Hiding/showing fields based on initial conditions
  • Setting fields to read-only
  • Displaying informational messages

Performance Note: Keep onLoad scripts lightweight—they impact form load time.

2. onChange Scripts

Runs: When the value of a specified field changes.

Best for:

  • Cascading field updates
  • Dynamic field validations
  • Conditional field visibility
  • Auto-populating related fields

Important: Only applies to the specific field defined in the script.

3. onSubmit Scripts

Runs: When a user submits a form, before data reaches the server.

Best for:

  • Final form validations
  • Confirming user actions
  • Preventing submission based on conditions
  • Displaying warning messages

Return Value: Must return true to allow submission or false to prevent it.

4. onCellEdit Scripts

Runs: When a user changes a field value in a list editor.

Best for:

  • List view validations
  • Updating related list fields
  • Immediate feedback in list views

10 Real-Time Examples

Example 1: Auto-Populate Description Based on Category (onChange)

Use Case: When a user selects a category on an Incident form, automatically populate the description field with a template.

Type: onChange | Table: Incident [incident] | Field: Category

function onChange(control, oldValue, newValue, isLoading, isTemplate) {
    if (isLoading || newValue == '') {
        return;
    }
    var category = g_form.getValue('category');
    var templates = {
        'hardware': 'Hardware Issue:\n\nDevice Type:\nSerial Number:\nIssue Description:\nSteps to Reproduce:',
        'software': 'Software Issue:\n\nApplication Name:\nVersion:\nError Message:\nSteps to Reproduce:',
        'network': 'Network Issue:\n\nLocation:\nDevices Affected:\nIssue Description:\nStart Time:'
    };
    if (templates[category]) {
        g_form.setValue('description', templates[category]);
    }
}

Key Takeaways:

  • Always check isLoading to prevent execution during form load
  • Validate that newValue exists before processing
  • Use clear, structured templates

Example 2: Mandatory Field Based on Condition (onLoad & onChange)

Use Case: Make the “Justification” field mandatory when Priority is “Critical” or “High”.

Type: onChange | Table: Change Request [change_request] | Field: Priority

function onChange(control, oldValue, newValue, isLoading, isTemplate) {
    if (isLoading || newValue == "") {
        return;
    }
    var priority = g_form.getValue('priority');
    if (priority == '1' || priority == '2') {
        g_form.setMandatory('justification', true);
        g_form.showFieldMsg('justification', 'Justification is required for high-priority changes', 'info');
    } else {
        g_form.setMandatory('justification', false);
        g_form.hideFieldMsg('justification');
    }
}

Companion onLoad Script:

function onLoad() {
    var priority = g_form.getValue('priority');
    if (priority == '1' || priority == '2') {
        g_form.setMandatory('justification', true);
    }
}

Key Takeaways:

  • Use both onLoad and onChange for consistency
  • Provide feedback with showFieldMsg()
  • Priority values are stored as numbers

Example 3: Prevent Form Submission with Validation (onSubmit)

Use Case: Prevent submitting an Incident without a Configuration Item when category is “Hardware”.

Type: onSubmit | Table: Incident [incident]

function onSubmit() {
    var category = g_form.getValue('category');
    var configItem = g_form.getValue('cmdb_ci');
    if (category == 'hardware' && configItem == '') {
        g_form.addErrorMessage('Please select a Configuration Item for hardware-related incidents.');
        g_form.flash('cmdb_ci', '#FF0000', 0);
        return false;
    }
    return true;
}

Key Takeaways:

  • Return false to prevent submission, true to allow it
  • Use addErrorMessage() for clear communication
  • Use flash() to draw attention to problematic fields

Example 4: Hide/Show Fields Based on Selection (onChange)

Use Case: Show additional fields only when “Other” is selected in a dropdown.

Type: onChange | Table: Incident [incident] | Field: Close Code

function onChange(control, oldValue, newValue, isLoading, isTemplate) {
    if (isLoading || newValue == "") {
        return;
    }
    var closeCode = g_form.getValue('close_code');
    if (closeCode == 'other') {
        g_form.setVisible('close_notes', true);
        g_form.setMandatory('close_notes', true);
        g_form.showFieldMsg('close_notes', 'Please provide detailed closure notes', 'info');
    } else {
        g_form.setVisible('close_notes', false);
        g_form.setMandatory('close_notes', false);
        g_form.setValue('close_notes', '');
    }
}

Key Takeaways:

  • Clear hidden field values to prevent data confusion
  • Remove mandatory status when hiding fields
  • Always explain why fields appear

Example 5: Copy Values Between Fields (onChange)

Use Case: When “Use Caller’s Location” is checked, copy the caller’s location to the incident location.

Type: onChange | Table: Incident [incident]

function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading || newValue == 'false') {
        return;
    }
    var callerSysId = g_form.getValue('caller_id');
    if (callerSysId) {
        var ga = new GlideAjax('LocationUtilsAjax');
        ga.addParam('sysparm_name', 'getCallerLocation');
        ga.addParam('sysparm_caller_id', callerSysId);
        ga.getXMLAnswer(function(answer) {
            if (answer) {
                g_form.setValue('location', answer);
            }
        });
    }
}

Alternative Direct Copy:

function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading || newValue == 'false') {
        return;
    }
    var callerLocation = g_form.getReference('caller_id').location;
    if (callerLocation) {
        g_form.setValue('location', callerLocation);
    }
}

Key Takeaways:

  • Use GlideAjax for complex server-side data retrieval
  • Use getReference() for simple reference field access
  • Always check if source values exist

Example 6: Real-Time Field Validation (onChange)

Use Case: Validate email format as the user types.

Type: onChange | Table: User [sys_user] | Field: Email

function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading || newValue == '') {
        return;
    }
    var emailPattern = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/;
    if (!emailPattern.test(newValue)) {
        g_form.showFieldMsg('email', 'Please enter a valid email address', 'error');
        g_form.flash('email', '#FF0000', 0);
    } else {
        g_form.hideFieldMsg('email');
        g_form.showFieldMsg('email', 'Email format is valid', 'success');
        setTimeout(function() {
            g_form.hideFieldMsg('email');
        }, 3000);
    }
}

Key Takeaways:

  • Use regular expressions for pattern validation
  • Provide both error and success feedback
  • Consider timed message removal for UX

Example 7: Calculate and Display Total Cost (onChange)

Use Case: Calculate total cost from quantity and unit price on a Purchase Order.

Type: onChange | Table: Purchase Order Line [sc_req_item]

function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading) {
        return;
    }
    calculateTotal();
}

function calculateTotal() {
    var quantity = parseFloat(g_form.getValue('quantity')) || 0;
    var unitPrice = parseFloat(g_form.getValue('price')) || 0;
    var total = quantity * unitPrice;
    g_form.setValue('total_cost', total.toFixed(2));
    if (quantity > 0 && unitPrice > 0) {
        g_form.showFieldMsg('total_cost',
            quantity + ' × $' + unitPrice.toFixed(2) + ' = $' + total.toFixed(2),
            'info');
    }
}

Key Takeaways:

  • Use parseFloat() for numeric calculations
  • Provide defaults with || 0 to prevent NaN errors
  • Use toFixed(2) for currency formatting
  • Create reusable functions for shared logic

Example 8: Confirmation Dialog for Critical Actions (onSubmit)

Use Case: Confirm before closing a critical incident.

Type: onSubmit | Table: Incident [incident]

function onSubmit() {
    var state = g_form.getValue('state');
    var priority = g_form.getValue('priority');
    var resolution = g_form.getValue('close_notes');
    if (state == '7' && priority == '1') {
        if (!resolution || resolution.length < 50) {
            g_form.addErrorMessage('Critical incidents require detailed resolution notes (minimum 50 characters).');
            return false;
        }
        var confirmMsg = 'You are closing a Critical incident. Please confirm:\n\n' +
                        '• Root cause has been identified\n' +
                        '• Proper resolution has been documented\n' +
                        '• User has confirmed resolution\n\n' +
                        'Proceed with closing this incident?';
        if (!confirm(confirmMsg)) {
            return false;
        }
    }
    return true;
}

Key Takeaways:

  • Use confirm() for important user confirmations
  • Validate completeness of critical fields
  • Provide clear checklist items

Example 9: Dynamic Field Options via GlideAjax (onChange)

Use Case: Filter available assignment groups based on the selected category.

Type: onChange | Table: Incident [incident] | Field: Category

function onChange(control, oldValue, newValue, isLoading, isTemplate) {
    if (isLoading || newValue == '') {
        return;
    }
    g_form.clearValue('assignment_group');
    var ga = new GlideAjax('IncidentUtils');
    ga.addParam('sysparm_name', 'getAssignmentGroups');
    ga.addParam('sysparm_category', newValue);
    ga.getXMLAnswer(function(answer) {
        if (answer) {
            var groups = JSON.parse(answer);
            if (groups.length == 1) {
                g_form.setValue('assignment_group', groups[0].sys_id);
                g_form.showFieldMsg('assignment_group',
                    'Auto-assigned to: ' + groups[0].name, 'info');
            } else if (groups.length > 1) {
                g_form.showFieldMsg('assignment_group',
                    groups.length + ' assignment groups available for this category', 'info');
            } else {
                g_form.showFieldMsg('assignment_group',
                    'No specific assignment group found for this category', 'warning');
            }
        }
    });
}

Required Script Include (IncidentUtils):

var IncidentUtils = Class.create();
IncidentUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {
    getAssignmentGroups: function() {
        var category = this.getParameter('sysparm_category');
        var groups = [];
        var gr = new GlideRecord('sys_user_group');
        gr.addQuery('active', true);
        gr.addQuery('u_supported_category', category);
        gr.query();
        while (gr.next()) {
            groups.push({
                sys_id: gr.getUniqueValue(),
                name: gr.getValue('name')
            });
        }
        return JSON.stringify(groups);
    },
    type: 'IncidentUtils'
});

Key Takeaways:

  • GlideAjax enables server-side data retrieval
  • Always parse JSON responses properly
  • Clear dependent fields when parent changes

Example 10: Set Read-Only Fields Based on User Role (onLoad)

Use Case: Lock certain fields for users without specific roles.

Type: onLoad | Table: Change Request [change_request]

function onLoad() {
    var hasChangeManagerRole = g_user.hasRole('change_manager');
    var hasAdminRole = g_user.hasRole('admin');

    if (!hasChangeManagerRole && !hasAdminRole) {
        g_form.setReadOnly('approval', true);
        g_form.setReadOnly('risk', true);
        g_form.setReadOnly('impact', true);
        g_form.addInfoMessage('Risk and approval fields are managed by Change Managers only.');
        g_form.setSectionDisplay('approval_section', false);
    }

    var state = g_form.getValue('state');
    if (state == 'implement' || state == 'closed') {
        var fieldsToLock = ['short_description', 'description', 'start_date', 'end_date'];
        fieldsToLock.forEach(function(field) {
            g_form.setReadOnly(field, true);
        });
        g_form.addInfoMessage('This change is in implementation phase. Most fields are locked.');
    }
}

Key Takeaways:

  • Use g_user.hasRole() for role-based logic
  • Combine conditions for complex scenarios
  • Inform users why fields are read-only

Best Practices for Client Scripts

1. Always Check the isLoading Parameter

function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading) {
        return;
    }
    // Your code here
}

Prevents unnecessary execution and potential infinite loops.

2. Minimize GlideAjax Calls

  • Cache results when possible
  • Combine multiple data requests into single calls
  • Use asynchronous callbacks properly

Best Practice:

var ga = new GlideAjax('Utils');
ga.addParam('sysparm_name', 'getMultipleValues');
ga.getXMLAnswer(function(answer) {
    var data = JSON.parse(answer);
    // Process all data at once
});

3. Use Descriptive Naming and Comments

function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading) {
        return;
    }
    var priorityValue = g_form.getValue('priority');
    if (priorityValue == '1') {
        enforceImmediateAssignment();
    }
}

function enforceImmediateAssignment() {
    g_form.setMandatory('assigned_to', true);
    g_form.showFieldMsg('assigned_to',
        'Critical priority requires immediate assignment', 'error');
}

4. Validate Input and Handle Edge Cases

function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading || newValue == '' || newValue == oldValue) {
        return;
    }
    var quantity = parseInt(newValue);
    if (isNaN(quantity) || quantity < 0) {
        g_form.showFieldMsg('quantity', 'Please enter a valid positive number', 'error');
        g_form.setValue('quantity', oldValue);
        return;
    }
}

5. Avoid Large Data in g_scratchpad

Use g_scratchpad only for small, essential data—it increases page size.

6. Test with Different User Roles and Scenarios

Test as different user types, with various data combinations, on mobile and desktop.

7. Use UI Policies When Possible

UI Policy for: Simple show/hide, basic mandatory rules, read-only settings, simple field clearing.

Client Scripts for: Complex calculations, multi-field validations, AJAX calls, custom messages, advanced conditional logic.

8. Optimize Performance

// Better: Store in variable
var priority = g_form.getValue('priority');
if (priority == '1' || priority == '2') {
    // Logic
}

9. Handle Errors Gracefully

function onChange(control, oldValue, newValue, isLoading) {
    try {
        if (isLoading) return;
        var result = performCalculation(newValue);
        g_form.setValue('result', result);
    } catch (error) {
        g_form.addErrorMessage('An error occurred. Please contact support.');
        console.error('Client Script Error:', error);
    }
}

10. Document Your Scripts

/*
 * Client Script: Auto-populate Assignment Group
 * Type: onChange
 * Table: Incident
 * Field: Category
 *
 * Description: When category changes, automatically suggests appropriate
 * assignment group based on category-to-group mapping table.
 *
 * Author: Your Name
 * Date: 2024-01-15
 */

Common Troubleshooting Tips

Issue 1: Script Not Executing

Possible Causes:

  • Script is inactive → Enable the “Active” checkbox
  • Wrong table → Verify the “Table” field matches your form
  • Conditions not met → Test without conditions first
  • UI Policy conflict → Adjust execution order

Debug with:

console.log('Script executing');
console.log('Old Value:', oldValue);
console.log('New Value:', newValue);
console.log('Is Loading:', isLoading);

Issue 2: Infinite Loop or Performance Problems

Causes:

  • Missing isLoading check
  • Circular onChange scripts (Field A sets B, B sets A)
  • Too many GlideAjax calls (add debouncing)

Issue 3: GlideAjax Not Working

Checklist:

  • ✓ “Client callable” checkbox is selected
  • ✓ Script Include extends AbstractAjaxProcessor
  • ✓ Function name matches sysparm_name parameter
  • ✓ Callback function is properly defined

Issue 4: Fields Not Updating

  • Verify field names are correct (use browser inspect)
  • Reference fields require sys_id, not display value
  • Timing issues may require setTimeout()

Issue 5: Works on Desktop, Not Mobile

  • Avoid confirm(), alert(), prompt()
  • Use g_form.addInfoMessage() and g_form.showFieldMsg()
  • Detect mobile with g_form.isMobile()

Issue 6: Mandatory Field Not Preventing Submission

// WRONG - Missing return
function onSubmit() {
    if (validateForm()) {
        false; // Does nothing!
    }
}

// CORRECT
function onSubmit() {
    if (!validateForm()) {
        return false;
    }
    return true;
}

Pro Tip: Enable Client Script Debugging

  1. Navigate to System Properties > UI Properties
  2. Search for glide.ui.security.allow_client_script_debugging
  3. Set to true
  4. Add &sysparm_client_script_debug=true to the URL

Frequently Asked Questions

What is the difference between Client Scripts and Business Rules?
Client Scripts run on the client-side (browser) for form interactions and immediate feedback. Business Rules run server-side for data validation, complex logic, and database operations.

Can I use server-side APIs in Client Scripts?
No. Use GlideAjax to call Script Includes that execute server-side code.

How many Client Scripts can I have on a single form?
There’s no hard limit, but too many impact performance. Keep under 10 per form and consolidate logic.

Will Client Scripts work on Service Portal?
No. Use Catalog Client Scripts or Widget Controllers instead.

How do I debug Client Scripts?
Use browser Developer Tools (F12), check the Console tab, add console.log() statements, and enable ServiceNow’s client script debugging.


Conclusion

Mastering ServiceNow Client Scripts is essential for building efficient, user-friendly applications. Key takeaways:

  • Choose the Right Type: onLoad, onChange, onSubmit, or onCellEdit
  • Always Validate: Check isLoading, validate inputs, handle edge cases
  • Optimize Performance: Minimize AJAX calls, cache values
  • Provide User Feedback: Use informative messages and visual cues
  • Test Thoroughly: Verify across roles, scenarios, and devices

Start small, test in sub-production, document everything, and monitor performance. Happy scripting!

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top