approval-workflows
SKILL.md
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
Weekly Installs
53
Repository
groeimetai/snow-flowGitHub Stars
53
First Seen
Jan 22, 2026
Security Audits
Installed on
opencode48
codex48
gemini-cli48
claude-code47
github-copilot47
cursor46