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_request when 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 IN queries with addQuery('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: requested is 'requested', approved is 'approved', rejected is 'rejected'—use toString() for string comparison
  • Always include table field in your query—document_id alone 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_approver may filter results based on user context—run elevated if needed with gs.setProperty()
  • One rejection should immediately fail the entire approval process—use early break to 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.

Free Newsletter

Enjoying this? Get one deep-dive per week.

Join 1,000+ ServiceNow pros — scripts, GlideRecord patterns, Flow Designer techniques, and career moves. Free.

No spam · Unsubscribe anytime