The Business Rule execution model
Business Rules are server-side JavaScript that execute in response to database operations — insert, update, delete, and query. The four types differ in exactly when they run relative to the database transaction, and what that timing enables or prevents.
The database transaction lifecycle for a record save:
- User submits the form (or a script calls insert()/update())
- Before Business Rules fire — record not yet committed
- Record is written to the database — transaction commits
- After Business Rules fire — record is now in the database
- Async Business Rules are queued — run in a separate thread, user does not wait
Display Business Rules are separate — they fire when a form is loaded, not on a save operation.
Before Business Rules
Before Business Rules fire before the record is written to the database. The database transaction is open, the record has not been committed, and you can still modify what gets saved.
What you can do in a Before rule
- Modify field values on current — set values that will be saved as part of the transaction, with no extra update() call needed
- Abort the save — call
current.setAbortAction(true)to cancel the entire operation - Validate data — check conditions and abort with an error message if they fail
- Compute derived fields — calculate a priority from urgency and impact, derive a category from a symptom, set an audit timestamp
What you cannot do (or should not do) in a Before rule
- Do not call current.update() — the platform will save the record automatically after Before rules complete. Calling update() inside a Before rule causes a redundant extra database write and can trigger additional Business Rules
- Avoid external API calls — Before rules block the save transaction. A REST call that takes 3 seconds means the user waits 3 additional seconds before the save confirms. Use Async or Flow Designer for external calls
- Avoid querying large tables without limits — same reason as above; slow queries block the save
Examples
// Set resolved_at timestamp when state changes to Resolved
(function executeRule(current, previous) {
if (current.state == '6' && previous.state != '6') {
current.setValue('resolved_at', new GlideDateTime());
current.setValue('resolved_by', gs.getUserID());
}
})(current, previous);
// Auto-calculate priority from urgency and impact
(function executeRule(current, previous) {
var urgency = parseInt(current.getValue('urgency'));
var impact = parseInt(current.getValue('impact'));
// Priority matrix: 1+1=1, 1+2=2, 2+2=2, 2+3=3, 3+3=3 etc.
var matrix = [[1,2,2],[2,2,3],[3,3,4]];
var priority = matrix[urgency-1][impact-1];
current.setValue('priority', priority.toString());
})(current, previous);
// Abort save if required field is missing for resolved state
(function executeRule(current, previous) {
if (current.state == '6' && !current.getValue('close_code')) {
current.setAbortAction(true);
gs.addErrorMessage('Close code is required to resolve this incident.');
}
})(current, previous);
After Business Rules
After Business Rules fire after the record has been committed to the database. The triggering transaction is complete, current reflects the saved values, and related operations are safe to perform.
What you can do in an After rule
- Create or update related records — create a task, update a parent record, write to an audit table
- Modify current — but you must call current.update() explicitly, which causes an additional database write
- Send notifications — though Async or Flow Designer is usually better for this
- Trigger integrations — though Async or Flow Designer handles errors and retry better
Key difference from Before: current.update() is required
// After rule — must call current.update() to persist changes to current
(function executeRule(current, previous) {
if (current.state == '6' && previous.state != '6') {
// Create a follow-up task
var task = new GlideRecord('task');
task.setValue('parent', current.sys_id);
task.setValue('short_description', 'Follow up: ' + current.getValue('number'));
task.setValue('assigned_to', current.getValue('assigned_to'));
task.insert();
// If you also need to update current, call update() explicitly
current.setValue('follow_up_task', task.sys_id);
current.update(); // Required in After rules
}
})(current, previous);
Calling current.update() in an After rule triggers another round of Business Rules on that record. Be aware of this chain and use setWorkflow(false) if you want to avoid triggering the full Business Rule chain again:
current.setWorkflow(false); // Prevents re-triggering Business Rules
current.autoSysFields(false); // Prevents updating sys_updated_on etc.
current.update();
Async Business Rules
Async Business Rules run after the record saves, in a separate background thread. The user's save operation completes and the page confirms immediately — the Async rule runs in the background without blocking the user interface.
When to use Async rules
- Expensive operations: complex multi-table queries, large data processing, generating reports
- External API calls: any operation where a slight delay is acceptable
- Email and notifications: when the send does not need to block the save
- Bulk updates: updating many related records that would be slow in a synchronous rule
The async rule limitation — previous
This is the most important thing to know about Async rules. By the time an Async rule runs, the "previous" values of the record may no longer be reliably available — other saves may have occurred to that record in the intervening time. Do not use previous for important logic in Async rules. See the complete current vs previous guide for a detailed explanation.
Async rule example
// Async rule — user doesn't wait for this
(function executeRule(current, previous) {
// Safe to do expensive work here
var ga = new GlideAggregate('incident');
ga.addEncodedQuery('assignment_group=' + current.getValue('assignment_group') + '^active=true');
ga.addAggregate('COUNT');
ga.query();
if (ga.next()) {
var groupLoad = parseInt(ga.getAggregate('COUNT'));
// Update group load metric
var groupGr = new GlideRecord('sys_user_group');
if (groupGr.get(current.getValue('assignment_group'))) {
groupGr.setValue('u_current_load', groupLoad);
groupGr.setWorkflow(false);
groupGr.update();
}
}
})(current, previous);
Display Business Rules
Display Business Rules run when a record is loaded in a form view — before the form renders in the browser. They are the only Business Rule type that runs on a query operation rather than a data modification, and the only type that can communicate data to Client Scripts via the g_scratchpad object.
What Display rules are for
The primary use case: pre-loading server-side data that a Client Script will need on form load, without requiring a GlideAjax round-trip after the form opens. This improves form load performance when the Client Script would otherwise need to make an Ajax call immediately on load.
// Display rule — fires when form loads
(function executeRule(current, previous) {
// Send data to client scripts via g_scratchpad
g_scratchpad.is_vip_caller = (current.caller_id.vip == true);
g_scratchpad.open_incident_count = getOpenCount(current.caller_id.toString());
g_scratchpad.caller_department = current.caller_id.department.getDisplayValue();
g_scratchpad.user_can_resolve = gs.hasRole('itil_admin');
})(current, previous);
function getOpenCount(userId) {
var ga = new GlideAggregate('incident');
ga.addEncodedQuery('active=true^caller_id=' + userId);
ga.addAggregate('COUNT');
ga.query();
return ga.next() ? parseInt(ga.getAggregate('COUNT')) : 0;
}
// Then in a Client Script (onLoad):
function onLoad() {
if (g_scratchpad.is_vip_caller) {
g_form.showFieldMsg('caller_id', 'VIP Caller — expedite handling', 'info');
}
if (g_scratchpad.open_incident_count > 5) {
g_form.showFieldMsg('caller_id', 'This caller has ' + g_scratchpad.open_incident_count + ' open incidents', 'warning');
}
}
Display rule limitations
- Only fires on form view, not on list views or API access
- Does not have access to
previous— the record has not been modified - Cannot modify the record — it runs on query, not insert/update
- Keep scripts lightweight — Display rules run on every form load and slow them down if heavy
Business Rule configuration options
The When setting
Determines when the rule fires: Before, After, Async, or Display.
The Insert/Update/Delete/Query checkboxes
Controls which database operations trigger the rule. A rule with only Update checked does not fire when a new record is created. Display rules use Query.
The Condition field
A filter condition evaluated before the script runs. If the condition is false, the script does not execute. Always use conditions to narrow when a rule fires — it is more efficient than checking conditions inside the script.
// Less efficient — condition checked inside script
(function executeRule(current, previous) {
if (current.priority == '1' && current.state != previous.state) {
// do work
}
})(current, previous);
// More efficient — condition set in the Condition field on the BR record
// Condition: Priority = 1 AND State changes
(function executeRule(current, previous) {
// Script only runs when condition is already true
// do work
})(current, previous);
The Order field
Controls the sequence when multiple Business Rules of the same type fire on the same table and operation. Lower numbers run first. Default is 100. If two Before rules must run in a specific sequence, set explicit order values (e.g., 100 and 200).
Note: ServiceNow does not guarantee execution order between rules with the same order value. Always use explicit order numbers when sequence matters.
Advanced settings — Filter conditions vs Script
Both the Condition field and the script's conditional logic do the same thing — they determine whether work happens. The difference is performance: the Condition field is evaluated as a SQL-level filter before the script even loads. A script-level condition requires loading and executing JavaScript. Put your narrowest conditions in the Condition field, not just in the script.
Business Rules vs Flow Designer — when to use which
Before rules that modify the record or abort the save are the core remaining use case for Business Rules. For everything else — notifications, related record updates, external calls, multi-step processes — Flow Designer is the better tool. See the complete Flow Designer vs Business Rules guide for the full decision framework.
Common Business Rule mistakes
Mistake 1 — Calling current.update() in a Before rule
The platform saves current automatically after Before rules complete. Calling current.update() in a Before rule causes an additional database write and re-triggers the rule chain.
Mistake 2 — Making external API calls in Before or After rules
Both block the user's save operation. Move external calls to Async rules or Flow Designer.
Mistake 3 — Creating infinite loops
// After rule that calls current.update() — without setWorkflow(false)
// This triggers the After rule again → triggers update again → infinite loop
// Fix:
current.setWorkflow(false); // Break the loop
current.update();
Mistake 4 — Using previous in Async rules
As described above, previous is unreliable in Async rules. For the full explanation and workarounds, see current vs previous in Business Rules.
Mistake 5 — Not checking the operation type
// Rule fires on Insert and Update but previous is empty on Insert
// Always check operation when using previous
if (current.operation() == 'update' && current.state != previous.state) {
// Safe — only runs on updates, where previous has meaningful values
}
The quick decision guide
- Modify the current record before save → Before
- Abort the save based on a condition → Before with setAbortAction()
- Create/update related records, user waits → After
- Expensive work, user should not wait → Async
- Send data to Client Scripts on form load → Display
- Notifications, integrations, multi-step processes → Flow Designer
Related guides:
- current vs previous in Business Rules — the full reference on what each object contains
- Flow Designer vs Business Rules — when to use each tool
- Script Includes — extracting Business Rule logic into reusable libraries
- Debugging ServiceNow scripts — debugging Business Rule logic
- GlideRecord performance tips — writing efficient queries in Business Rules