ServiceNow stores catalog request approvals in sysapproval_approver records linked by document_id to the original request. The critical insight: you need to query ALL approval records for a request to determine overall status—a single approved record doesn't mean the request is approved if other approvals are still pending. Most developers make the mistake of checking only the first approval record or assuming state values are strings when they're actually stored as integers with display values.
When to use this pattern
- In Business Rules on
sc_requestwhen approval state changes and you need to update request status - In Scheduled Jobs that process batches of requests awaiting approval decisions
- When building approval dashboards or reports that need real-time status aggregation
- In Fix Scripts cleaning up orphaned approvals or correcting approval states after data migration
When NOT to use this pattern
- Don't use in Client Scripts—use GlideAjax to call a Script Include that performs this server-side query
- Don't query individual approval status in tight loops—batch your requests and use
INqueries withaddQuery('document_id', 'IN', requestIds) - Don't use when you only need approval counts—use GlideAggregate with
addAggregate('COUNT')grouped by state - Don't run this in Before Business Rules on
sysapproval_approver—you'll create infinite recursion when updating the request
Key behaviors and gotchas
- State field stores integers:
requestedis 'requested',approvedis 'approved',rejectedis 'rejected'—usetoString()for string comparison - Always include
tablefield in your query—document_idalone can return approvals for other record types with same sys_id - Approval records persist after request completion—don't assume empty results mean no approvals were required
- ACLs on
sysapproval_approvermay filter results based on user context—run elevated if needed withgs.setProperty() - One rejection should immediately fail the entire approval process—use early
breakto avoid processing remaining records - Domain separation applies to approval records—queries may return partial results in multi-domain environments
Never call current.update() inside a Business Rule without checking if you're already in an update operation. Use current.autoSysFields = false; to prevent infinite loops when updating the same record that triggered the rule.
Enjoying this? Get one deep-dive per week.
Join 1,000+ ServiceNow pros — scripts, GlideRecord patterns, Flow Designer techniques, and career moves. Free.