Approval Workflows for ServiceNow
SkillDev toolsConfigure ServiceNow approval rules and sysapproval_approver records — manager/group/script approvers, multi-level routing, delegation via sys_user_delegate, and parent-record state rollup.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Approval Workflows for ServiceNow skill
What this skill tells your AI
The instructions your AI receives, as published by serac-labs/serac in packages/skills/approval-workflows/SKILL.md and read by ahel’s review.
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 and approval rules |
snow_approval_chain_manage | Define multi-step approval chains (define_chain) |
snow_artifact_manage | Find approval business rules (action='find') |
snow_execute_script | 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({
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
Signals
- GitHub stars
- 78
- Forks
- 26
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
approval-workflows-serac-labs- Source
- github.com/serac-labs/serac