Client Scripts vs. Business Rules: The Ultimate Guide
Introduction
One of the most frequently debated topics among ServiceNow developers and administrators is the choice between Client Scripts and Business Rules. Both automate logic, both can manipulate data, and both can fire on form interactions—yet confusing the two is one of the fastest ways to introduce hard-to-debug performance problems, security gaps, and unpredictable behavior in your instance.
This guide walks you through the full picture: what each tool does, when to use which, common patterns, anti-patterns, and a clear decision framework you can bookmark and share with your team.
What Is a Client Script?
A Client Script executes in the user's browser (the client side). It runs JavaScript that directly interacts with the form UI—showing/hiding fields, setting values, alerting users, and validating input before the record is ever saved.
Types of Client Scripts
| Type | When It Fires |
|---|---|
onLoad |
When a form is opened or refreshed |
onChange |
When a specific field value changes |
onSubmit |
When the user clicks Submit |
onCellEdit |
When a cell is edited in a list view |
Key Client-Side APIs
// Get a field value
var priority = g_form.getValue('priority');
// Set a field value
g_form.setValue('state', '2');
// Show or hide a field
g_form.setVisible('close_notes', false);
// Make a field read-only
g_form.setReadOnly('assigned_to', true);
// Show an informational message
g_form.addInfoMessage('Please fill in the description before submitting.');
// Flash a field to draw attention
g_form.flash('priority', '#FF0000', 0);
When to Use Client Scripts
- Real-time UI feedback: Instantly show/hide fields based on user selection.
- Input validation before save: Warn users about missing or invalid data without a server round-trip.
- Dynamic form behavior: Set default values, populate dependent fields, or calculate totals on the fly.
- User experience polish: Improve form usability without touching server logic.
Client Script Pitfalls to Avoid
- Never use synchronous GlideRecord in Client Scripts. Synchronous calls block the browser thread and are deprecated. Always use GlideAjax for server lookups.
- Don't rely on Client Scripts for security. Users with browser dev tools can bypass client-side logic entirely. Security belongs in Business Rules and ACLs.
- Avoid heavy logic on
onLoad. Too muchonLoadlogic slows form rendering. If a field calculation can happen server-side, do it there.
What Is a Business Rule?
A Business Rule executes on the server in response to database operations—insert, update, delete, or query. It runs Rhino-based server-side JavaScript with access to the full GlideRecord API, system properties, and server utilities.
Business Rule Timing Options
| Timing | Description |
|---|---|
before |
Runs before the record is written to the database. Changes affect the saved record. |
after |
Runs after the record is written. Cannot modify the saved record directly (use current.update() carefully). |
async |
Runs asynchronously after the transaction completes. Best for notifications, integrations, and expensive operations. |
display |
Runs when a record is loaded for display. Used to set g_scratchpad values for Client Scripts. |
Core Business Rule APIs
// Access the current record being processed
var incidentNumber = current.getValue('number');
// Access the record's previous values (on update)
var oldState = previous.getValue('state');
var newState = current.getValue('state');
if (oldState != newState && newState == '6') {
// Incident just moved to Resolved
current.setValue('resolved_at', new GlideDateTime());
}
// Query related records
var task = new GlideRecord('task');
task.addQuery('parent', current.getUniqueValue());
task.query();
while (task.next()) {
gs.info('Related task: ' + task.getValue('number'));
}
// Log messages for debugging
gs.log('Business Rule fired for: ' + current.getValue('number'), 'MyBR');
gs.info('Info level message');
gs.warn('Warning: assignment group is empty');
gs.error('Critical failure in escalation logic');
When to Use Business Rules
- Data integrity: Enforce required field logic, auto-populate fields, and cascade updates.
- Complex calculations: Compute SLA deadlines, pricing, or risk scores during save.
- Cross-record operations: Update parent records, create related tasks, or trigger workflows.
- Security enforcement: Validate data that client scripts cannot be trusted to protect.
- Integration triggers: Initiate outbound REST calls or queue messages after a record change.
The Decision Framework
Ask yourself these questions when deciding which to use:
1. Does this need to run even when the record is updated via API or import?
→ YES: Use a Business Rule
→ NO: Client Script may be sufficient
2. Does this affect what the user sees on the form right now?
→ YES: Client Script (or Display Business Rule + g_scratchpad)
→ NO: Business Rule
3. Is this enforcing a security or data integrity rule?
→ YES: Business Rule (never rely on client-side for this)
→ NO: Either may work
4. Does this require querying another table to make a decision?
→ YES: Business Rule or GlideAjax (not synchronous client-side GlideRecord)
→ NO: Client Script is fine
g_scratchpad: Bridging the Gap
Sometimes you need server-computed data on the client side without making an async GlideAjax call. The g_scratchpad object is your bridge:
Display Business Rule (server side):
// Runs on display, populates scratchpad for client
g_scratchpad.isVIP = current.caller_id.vip.toString();
g_scratchpad.slaDeadline = current.sla_due.getDisplayValue();
Client Script (onLoad):
function onLoad() {
if (g_scratchpad.isVIP === 'true') {
g_form.addInfoMessage('This caller is a VIP. Handle with priority care.');
g_form.flash('priority', '#FFD700', 0);
}
}
This pattern avoids async calls while keeping server logic server-side.
Performance Considerations
Client Script Performance
- Minimize
onChangescripts that fire on every keystroke. - Cache
g_form.getValue()results in variables rather than calling repeatedly. - Avoid DOM manipulation outside the
g_formAPI.
Business Rule Performance
- Use
addQuery()conditions to filter records before your rule fires—don't rely solely on the Condition field for complex filtering. - Avoid recursive Business Rules (a rule that updates a record triggering itself again).
- Move any logic that doesn't need to block the transaction to
asynctiming. - Use
current.setAbortAction(true)sparingly and only inbeforerules when you need to prevent a save.
Real-World Pattern: Incident Priority Automation
Here's a complete, production-ready example combining both:
Business Rule — before on incident, insert/update:
// Auto-set priority based on impact and urgency
if (current.impact.changesTo() || current.urgency.changesTo()) {
var impact = parseInt(current.getValue('impact'));
var urgency = parseInt(current.getValue('urgency'));
var matrix = {
'11': '1', '12': '2', '13': '3',
'21': '2', '22': '3', '23': '4',
'31': '3', '32': '4', '33': '4'
};
var key = impact.toString() + urgency.toString();
if (matrix[key]) {
current.setValue('priority', matrix[key]);
}
}
Client Script — onChange on impact or urgency:
function onChange(control, oldValue, newValue, isLoading) {
if (isLoading) return;
var impact = g_form.getValue('impact');
var urgency = g_form.getValue('urgency');
if (impact == '1' && urgency == '1') {
g_form.addErrorMessage('Critical P1 incident! Notify the major incident manager immediately.');
}
}
The Business Rule enforces the matrix even when records are updated via API. The Client Script provides immediate user feedback on the form.
Common Pitfalls Summary
| Pitfall | Impact | Fix |
|---|---|---|
| Security logic in Client Scripts | Security bypass | Move to Business Rule or ACL |
| Synchronous GlideRecord in Client Scripts | Browser freeze | Use GlideAjax |
after Business Rule modifying current |
Silent failure | Use before or explicit current.update() |
Too many onLoad scripts |
Slow form load | Consolidate or move to Display BR |
| Recursive Business Rules | Infinite loop / timeout | Add a condition to prevent re-firing |
Conclusion
Client Scripts and Business Rules are complementary, not competing. Use Client Scripts for real-time UI behavior and user-facing feedback. Use Business Rules for data integrity, security, and server-side automation that must fire regardless of how a record is touched. Master the g_scratchpad bridge and GlideAjax for the cases that span both worlds, and your ServiceNow instance will be cleaner, faster, and far more maintainable.