Approval Workflows for ServiceNow
Approval workflows route records through configurable approval chains.
Approval Architecture
Record (change_request, sc_req_item, etc.) ↓ Approval Rules (sysapproval_rule) ↓ Approval Records (sysapproval_approver) ↓ Approve/Reject Record State Updated
Key Tables
Table Purpose
sysapproval_approver
Individual approval records
sysapproval_group
Group approval configuration
sysapproval_rule
Approval rules
sys_approval_workflow
Approval workflow stages
Approval Rules (ES5)
Create Approval Rule
// Create approval rule (ES5 ONLY!) var rule = new GlideRecord("sysapproval_rule") rule.initialize()
// Rule identification rule.setValue("name", "Change Request Manager Approval") rule.setValue("table", "change_request") rule.setValue("order", 100) rule.setValue("active", true)
// Conditions - when rule applies rule.setValue("conditions", "type=normal^priority<=2")
// Approver type rule.setValue("approver", "manager") // manager, group, user, script // For user: rule.setValue('approver_user', userSysId); // For group: rule.setValue('approver_group', groupSysId);
// Approval type rule.setValue("approval_type", "and") // and (all must approve), or (any can approve)
// Wait for previous level rule.setValue("wait_for", true)
rule.insert()
Script-Based Approver Selection
// Approval rule with script (ES5 ONLY!) var rule = new GlideRecord("sysapproval_rule") rule.initialize() rule.setValue("name", "Cost-Based Approval") rule.setValue("table", "sc_req_item") rule.setValue("approver", "script")
// Script to determine approvers (ES5 ONLY!) rule.setValue( "script", "(function getApprovers(current) {\n" + " var approvers = [];\n" + ' var cost = parseFloat(current.getValue("estimated_cost")) || 0;\n' + " \n" + " // Manager approval for all\n" + " var caller = current.requested_for.getRefRecord();\n" + " if (caller.manager) {\n" + " approvers.push(caller.manager.toString());\n" + " }\n" + " \n" + " // Director approval for > $5000\n" + " if (cost > 5000) {\n" + " var director = getDirector(caller);\n" + " if (director) approvers.push(director);\n" + " }\n" + " \n" + " // VP approval for > $25000\n" + " if (cost > 25000) {\n" + " var vp = getVP(caller);\n" + " if (vp) approvers.push(vp);\n" + " }\n" + " \n" + " return approvers;\n" + "})(current);", )
rule.insert()
Managing Approvals (ES5)
Create Approval Manually
// Create approval record (ES5 ONLY!) function createApproval(recordSysId, approverSysId, source) { var approval = new GlideRecord("sysapproval_approver") approval.initialize() approval.setValue("sysapproval", recordSysId) approval.setValue("approver", approverSysId) approval.setValue("state", "requested") approval.setValue("source_table", source.table || "")
return approval.insert() }
Process Approval Decision
// Approve or reject (ES5 ONLY!) function processApprovalDecision(approvalSysId, decision, comments) { var approval = new GlideRecord("sysapproval_approver") if (!approval.get(approvalSysId)) { return { success: false, message: "Approval not found" } }
// Validate current state if (approval.getValue("state") !== "requested") { return { success: false, message: "Approval already processed" } }
// Validate approver if (approval.getValue("approver") !== gs.getUserID()) { if (!canActOnBehalf(approval.getValue("approver"))) { return { success: false, message: "Not authorized to approve" } } }
// Set decision approval.setValue("state", decision) // 'approved' or 'rejected' approval.setValue("comments", comments) approval.setValue("actual_approver", gs.getUserID()) approval.update()
// Update parent record approval status updateParentApprovalStatus(approval.getValue("sysapproval"))
return { success: true, decision: decision, record: approval.sysapproval.getDisplayValue(), } }
function canActOnBehalf(originalApproverId) { // Check delegation var delegation = new GlideRecord("sys_user_delegate") delegation.addQuery("user", originalApproverId) delegation.addQuery("delegate", gs.getUserID()) delegation.addQuery("starts", "<=", new GlideDateTime()) delegation.addQuery("ends", ">=", new GlideDateTime()) delegation.addQuery("approvals", true) delegation.query() return delegation.hasNext() }
Update Parent Record
// Update approval status on parent record (ES5 ONLY!) function updateParentApprovalStatus(recordSysId) { // Get all approvals for this record var approvals = new GlideRecord("sysapproval_approver") approvals.addQuery("sysapproval", recordSysId) approvals.query()
var requested = 0 var approved = 0 var rejected = 0
while (approvals.next()) { var state = approvals.getValue("state") if (state === "requested") requested++ else if (state === "approved") approved++ else if (state === "rejected") rejected++ }
// Determine overall status var overallStatus = "not requested"
if (rejected > 0) { overallStatus = "rejected" } else if (requested > 0) { overallStatus = "requested" } else if (approved > 0) { overallStatus = "approved" }
// Update parent record var parent = new GlideRecord("change_request") if (parent.get(recordSysId)) { parent.setValue("approval", overallStatus) parent.update() } }
Group Approvals (ES5)
Configure Group Approval
// Create group approval configuration (ES5 ONLY!) var groupApproval = new GlideRecord("sysapproval_group") groupApproval.initialize() groupApproval.setValue("parent", recordSysId) groupApproval.setValue("group", groupSysId)
// Approval requirement groupApproval.setValue("approval", "any") // any, all, specific_count groupApproval.setValue("specific_count", 2) // If specific_count
groupApproval.insert()
Group Approval with Minimum
// Check if group approval threshold met (ES5 ONLY!) function checkGroupApprovalThreshold(groupApprovalSysId) { var groupConfig = new GlideRecord("sysapproval_group") if (!groupConfig.get(groupApprovalSysId)) { return false }
var approvalType = groupConfig.getValue("approval") var groupId = groupConfig.getValue("group") var parentId = groupConfig.getValue("parent")
// Count approvals from group members var ga = new GlideAggregate("sysapproval_approver") ga.addQuery("sysapproval", parentId) ga.addQuery("approver.sys_id", "IN", getGroupMembers(groupId)) ga.addQuery("state", "approved") ga.addAggregate("COUNT") ga.query()
var approvedCount = 0 if (ga.next()) { approvedCount = parseInt(ga.getAggregate("COUNT"), 10) }
// Check based on type if (approvalType === "any") { return approvedCount >= 1 } else if (approvalType === "all") { var memberCount = getGroupMemberCount(groupId) return approvedCount >= memberCount } else if (approvalType === "specific_count") { var required = parseInt(groupConfig.getValue("specific_count"), 10) return approvedCount >= required }
return false }
Approval Delegation (ES5)
Create Delegation
// Create approval delegation (ES5 ONLY!) function createDelegation(userId, delegateId, startDate, endDate) { var delegation = new GlideRecord("sys_user_delegate") delegation.initialize() delegation.setValue("user", userId) delegation.setValue("delegate", delegateId) delegation.setValue("starts", startDate) delegation.setValue("ends", endDate) delegation.setValue("approvals", true) delegation.setValue("assignments", false)
return delegation.insert() }
Find Active Delegates
// Get delegates who can approve for a user (ES5 ONLY!) function getActiveDelegates(userId) { var delegates = [] var now = new GlideDateTime()
var delegation = new GlideRecord("sys_user_delegate") delegation.addQuery("user", userId) delegation.addQuery("approvals", true) delegation.addQuery("starts", "<=", now) delegation.addQuery("ends", ">=", now) delegation.query()
while (delegation.next()) { delegates.push({ delegate: delegation.delegate.getDisplayValue(), delegate_id: delegation.getValue("delegate"), ends: delegation.getValue("ends"), }) }
return delegates }
Approval Notifications (ES5)
Send Approval Request
// Trigger approval notification (ES5 ONLY!) function sendApprovalNotification(approvalSysId) { var approval = new GlideRecord("sysapproval_approver") if (!approval.get(approvalSysId)) return
var parent = new GlideRecord(approval.source_table) if (parent.get(approval.getValue("sysapproval"))) { gs.eventQueue("approval.request", approval, approval.getValue("approver"), "") } }
MCP Tool Integration
Available Tools
Tool Purpose
snow_query_table
Query approvals
snow_find_artifact
Find approval rules
snow_execute_script_with_output
Test approval scripts
snow_create_business_rule
Create approval triggers
Example Workflow
// 1. Query pending approvals await snow_query_table({ table: "sysapproval_approver", query: "state=requested^approver=javascript:gs.getUserID()", fields: "sysapproval,state,sys_created_on", })
// 2. Find approval rules await snow_query_table({ table: "sysapproval_rule", query: "table=change_request^active=true", fields: "name,conditions,approver,approval_type", })
// 3. Check delegations
await snow_execute_script_with_output({
script: var delegates = getActiveDelegates(gs.getUserID()); gs.info('Active delegates: ' + JSON.stringify(delegates)); ,
})
Best Practices
-
Clear Conditions - Specific rule conditions
-
Logical Order - Rule ordering matters
-
Escalation - Handle non-response
-
Delegation - Support out-of-office
-
Notifications - Timely reminders
-
Audit Trail - Track all decisions
-
Testing - Test all approval paths
-
ES5 Only - No modern JavaScript syntax