Business Rules are server-side scripts that run when records are queried, updated, inserted, or deleted. They automate business logic and data processing.
Business Rules are THE most important server-side automation in ServiceNow. They run on the server, have full database access, and execute automatically based on conditions.
| Type | When | Use Case |
|---|---|---|
| Before | Before database write | Validate data, set field values, prevent save |
| After | After database write | Create related records, send notifications |
| Async | After commit, background | Long-running operations, external integrations |
| Display | When form loads | Set UI properties, control form behavior |
Run before the record is written to the database. Can modify field values or prevent the operation.
// Business Rule: Auto-assign High Priority Incidents
// Table: incident
// When: before
// Insert: true, Update: true
// Condition: current.priority == '1'
(function executeRule(current, previous /*null when async*/) {
// Ensure assignment for critical incidents
if (current.assignment_group.nil()) {
current.assignment_group = 'high_priority_group_sys_id';
gs.info('Auto-assigned high priority incident: ' + current.number);
}
// Set state to In Progress
if (current.state == '1') { // New
current.state = '2'; // In Progress
}
})(current, previous);
// Business Rule: Prevent Closing Without Resolution
// Table: incident
// When: before
// Update: true
// Condition: current.state == '7' (Closed)
(function executeRule(current, previous /*null when async*/) {
// Validate resolution information
if (current.close_notes.nil() || current.close_code.nil()) {
gs.addErrorMessage('Cannot close incident without resolution notes and close code');
current.setAbortAction(true); // Prevent save
return;
}
// Ensure resolved date is set
if (current.resolved_at.nil()) {
current.resolved_at = gs.nowDateTime();
}
})(current, previous);
Use for: data validation, setting field values, calculations. Do NOT query other tables excessively. Use current.setAbortAction(true) to prevent save. Changes to 'current' are saved automatically.
Run after the record is written to the database. Cannot modify the current record (must use update()).
// Business Rule: Create Incident Tasks
// Table: incident
// When: after
// Insert: true
// Condition: current.priority == '1'
(function executeRule(current, previous /*null when async*/) {
// Create initial investigation task
var task = new GlideRecord('incident_task');
task.initialize();
task.parent = current.sys_id;
task.short_description = 'Initial investigation for ' + current.number;
task.assignment_group = current.assignment_group;
task.priority = '1';
task.insert();
gs.info('Created task for critical incident: ' + current.number);
})(current, previous);
// Business Rule: Log Priority Changes
// Table: incident
// When: after
// Update: true
// Condition: current.priority.changes()
(function executeRule(current, previous /*null when async*/) {
// Log the priority change
var oldPriority = previous.priority.toString();
var newPriority = current.priority.toString();
gs.log('Priority changed from ' + oldPriority + ' to ' + newPriority +
' for incident: ' + current.number + ' by ' + gs.getUserName());
// Add work note
current.work_notes = 'Priority escalated from ' + oldPriority + ' to ' + newPriority;
current.update(); // Note: This triggers business rules again!
})(current, previous);
Use for: creating related records, triggering workflows, sending notifications. To modify current record, use current.update(). Be careful of infinite loops! Use .changes() to detect field modifications.
Run in the background after the database transaction completes. Don't block the user.
// Business Rule: External System Integration
// Table: incident
// When: async
// Insert: true, Update: true
// Condition: current.priority <= '2'
(function executeRule(current, previous /*null when async*/) {
// Note: 'previous' is not available in async
// Call external API
try {
var request = new sn_ws.RESTMessageV2();
request.setEndpoint('https://external-system.com/api/incident');
request.setHttpMethod('POST');
var body = {
number: current.number.toString(),
priority: current.priority.toString(),
description: current.short_description.toString()
};
request.setRequestBody(JSON.stringify(body));
var response = request.execute();
gs.info('Sent incident to external system: ' + response.getStatusCode());
} catch (ex) {
gs.error('Failed to send to external system: ' + ex.message);
}
})(current, previous);
Use for: external integrations, long-running operations, email sending. 'previous' is NOT available. User doesn't wait for completion. gs.addInfoMessage() won't display to user.
Run when a form is loaded, before it's sent to the client. Can use g_form methods.
// Business Rule: Set Form Properties
// Table: incident
// When: display
// Condition: current.state == '6' || current.state == '7'
(function executeRule(current, previous /*null when async*/) {
// Make fields read-only for closed incidents
g_scratchpad.isResolved = true;
// This data is available client-side via g_scratchpad
g_scratchpad.resolvedBy = current.resolved_by.getDisplayValue();
g_scratchpad.resolutionTime = current.resolved_at.toString();
})(current, previous);
Use for: passing data to client scripts via g_scratchpad. Limited use cases. Prefer UI Policies for form control. Don't perform heavy queries.
// Check if ANY field changed
if (current.changes()) {
gs.info('Record was modified');
}
// Check if specific field changed
if (current.priority.changes()) {
gs.info('Priority changed');
}
// Check if field changed FROM a value
if (current.state.changesFrom('2')) {
gs.info('State changed from In Progress');
}
// Check if field changed TO a value
if (current.state.changesTo('6')) {
gs.info('State changed to Resolved');
}
// Check if field changed from one value to another
if (current.state.changesFrom('2').changesTo('6')) {
gs.info('State changed from In Progress to Resolved');
}
// Get old value
if (current.priority.changes()) {
var oldValue = current.priority.getOldValue();
var newValue = current.priority.getValue();
gs.info('Priority changed from ' + oldValue + ' to ' + newValue);
}
// Using previous object (not available in async)
if (previous && previous.priority != current.priority) {
gs.info('Priority changed from ' + previous.priority + ' to ' + current.priority);
}
// Prevent save (before rules only)
current.setAbortAction(true);
// Prevent other business rules from running
current.setWorkflow(false);
// Check if running from async business rule
if (!gs.isInteractive()) {
// Running in background
}
// Order field: Controls execution order (100, 200, 300...)
// Lower numbers run first
// Advanced: Skip business rules on update
current.setWorkflow(false);
current.autoSysFields(false); // Don't update sys fields
current.update();
setAbortAction(true) - Stop savechanges() - Detect changesupdate() - Save in after/async