Approval routing starts simple. Then HR adds an exception for executives. Then finance carves out spend bands. Then legal wants their own path. Two years later your approval generator script is 800 lines of nested ifs that no one wants to touch. The fix is to externalize the routing decision before complexity sets in.
Decision Tables for the routing matrix
Use a Decision Table keyed on the dimensions that drive routing: amount band, requester department, item category, region. The output is the approval chain definition, not the approver itself. Business analysts can edit the table without engineering involvement, and the routing logic is reviewable as data instead of as code.
Approval chains are records, not scripts
Define each approval chain as a row in a u_approval_chain table with ordered steps: step number, approver source (user, group, manager-of-requester, dynamic script), required-or-informational, escalation-after-hours. The Decision Table picks a chain by name; the engine walks the steps. New chains are added without touching code.
Manager lookups need a fallback
requester.manager is null more often than people expect: contractors, executives at the top of the tree, accounts where the manager left the company. Every manager-step needs an explicit fallback: their manager’s manager, the requester’s department head, or a designated default approver. Failing closed (rejecting because manager is null) frustrates users and creates support tickets.
Delegations belong in the platform, not in your script
ServiceNow has built-in delegation. Use it instead of writing custom “out of office” logic. Custom delegation always has bugs, never integrates cleanly with the approval inbox, and breaks during clones when delegate references go stale.
Time-based escalation is a separate engine
Do not build escalation into the approval generator. Use Service Level Agreements (SLAs) attached to the approval task with breach actions that nudge, reassign, or auto-approve. SLA records have native dashboarding and audit; custom escalation timers do not.
Audit the path, not just the outcome
Log every routing decision with the inputs that produced it: amount, department, region, selected chain. When someone asks “why did this go to John instead of Mary?” you have a definitive answer. Without the inputs, the only option is to re-run the logic mentally, which is unreliable and slow.
Test the routing matrix as data, not code
Build an ATF suite that creates synthetic requests covering every cell of the Decision Table and verifies the resulting approval chain matches expectations. When someone edits the Decision Table, the suite re-runs and catches accidental changes to other cells. Routing tests as data are far more maintainable than tests as scripted requests.
Reject silently is the worst pattern
If an approval is rejected, the requester needs to know why and what to do next. Rejection comments should be required and surfaced in the email and portal. Ghost rejections train users to escalate through informal channels, which defeats the point of having an approval engine.
What to do this week: pull your approval generator script, count its lines, and sketch a Decision Table that would replace the top three branches.