1. Before, After, Async & Display Business Rules
A Business Rule is scoped to a table and an operation (insert, update, delete, query), and its type determines exactly when it fires relative to the database write:
- Before — runs before the record is saved, in the same transaction. Can modify
current's fields and those changes get saved too. Use for validation and field defaulting. - After — runs after the record is saved. Use for logic that acts on other records once this one is committed (e.g. updating a related record's count).
- Async — runs after the save, but off the user's transaction entirely, in a scheduled job. Use for anything slow (notifications, external calls) that shouldn't make the user wait.
- Display — runs before the form renders, pushing data to the client for a Client Script to use via
g_scratchpad. Rare; used when a Client Script needs server-only data at load time.
Async rules are tempting for anything that feels slow, but they run outside the user's transaction — if it fails, the user who triggered it never finds out. Use async for genuinely fire-and-forget work (an email notification), not for anything the user needs confirmation actually happened.
2. Execution Order
Multiple Business Rules of the same type on the same table run in the order set
by their numeric Order field (lower runs first; default is
100). When debugging "why did my rule not see the value I
expected," checking the order of every before-rule on that table is usually the
first move — a rule with a higher order number running after yours may have
already changed the field you're reading.
3. Detecting Field Changes Safely
It's tempting to reach for a raw "previous value" comparison, but the reliable,
well-documented idiom is the changes() family of methods available
on every GlideElement (i.e. every field on current):
if (current.state.changesTo(6)) { // 6 = Resolved
current.resolved_at = new GlideDateTime();
// (would need .update() here if this were a "before" rule instead;
// an "after" rule's direct field writes require an explicit update())
}
if (current.priority.changes()) {
gs.info('Priority changed from ' + current.priority.getJournalEntry(1));
}
changes() — did the field change at all this transaction.
changesTo(value) — did it change to this specific value.
changesFrom(value) — did it change from this specific
value. All three only make sense inside an update-triggered Business Rule; on
insert, every field "changes from nothing," which is rarely what you want to
branch on.
4. Avoiding Recursive Triggers
An after or before rule that updates the same
record it's running on (via current.update() in an after rule, for
instance) can re-trigger Business Rules on that table again — including itself,
if its own condition still matches. The platform has some built-in recursion
guards, but the safe habit is to make any such condition check
changesTo()/changesFrom() narrowly enough that it can't
match twice in a row for the same transition.
5. Hands-on Exercise
Auto-populate a resolution timestamp and notify on reopen
Requirements:
- Add a custom Date/Time field
u_resolved_atto the incident table. - Write a before Business Rule on update: when
state.changesTo(6)(Resolved), setcurrent.u_resolved_atto the current GlideDateTime. - Write a separate after Business Rule on update: when
state.changesFrom(6)back to In Progress (a "reopen"), log an info message (stand-in for a notification you'd build in a real system). - Give your reopen rule an Order value lower than 100, and explain in a comment why order matters here even though there's only one rule reacting to this transition.
u_resolved_at should only get set on the transition into Resolved, not every time the record is saved while already Resolved — changesTo() naturally handles this since it's only true on the transaction where the value actually changes.
6. Knowledge Check
Four quick questions. Expand each to check your answer.
Q1
What's the practical difference between a before and an after Business Rule?
What's the practical difference between a before and an after Business Rule?
A before rule runs prior to the database write, in the same transaction, and can modify current's own fields with those changes saved automatically as part of that same write. An after rule runs once the record is already committed, so any field changes it makes to current require an explicit update() call, and it's the right place for logic that touches other records.
Q2
Why are async Business Rules risky for anything the user needs confirmation about?
Why are async Business Rules risky for anything the user needs confirmation about?
Async rules run after the save but outside the user's own transaction, in a scheduled job. If the async logic fails, the user who triggered the save has no visibility into that failure — they already got their success response. Async is appropriate for genuinely fire-and-forget work, not anything requiring guaranteed completion the user is relying on.
Q3
Why is current.field.changesTo(value) generally safer than trying to compare against a raw "previous" value yourself?
Why is current.field.changesTo(value) generally safer than trying to compare against a raw "previous" value yourself?
changesTo() is a well-documented, built-in method on every GlideElement specifically designed for this comparison, and it correctly handles the update-only semantics (it's not meaningful on insert, where everything "changes from nothing"). Hand-rolling a comparison risks getting insert-vs-update edge cases wrong.
Q4
How can a Business Rule accidentally trigger itself, and what's the practical guard against it?
How can a Business Rule accidentally trigger itself, and what's the practical guard against it?
If a Business Rule updates the same record it's running on (e.g. current.update() in an after rule) and its own condition still matches the new state, it can re-trigger — including itself. Writing the condition as a narrow changesTo()/changesFrom() transition check, rather than a broad state-based condition, keeps it from matching again on the rule's own resulting update.