Dynamic Approval Routing in Salesforce

June 26, 2015 Appirio

By Bartee Natarajan

An approval process is used in Salesforce to initiate one or more collaborative business processes on a Salesforce record. It is very handy to choreograph the transition of the record between various stages, with specific individuals initiating the transition. An example of a standard approval process is an Opportunity that goes from a prospective sale to a fulfilled sale, with various users at different hierarchical levels approving or rejecting the transfer of the Opportunity between the stages.

A business process has one or more steps and each step usually has an approver (a user who has to approve or reject that step). The standard approach is to set the approver as the manager (in the standard manager field or a custom hierarchical relationship field) of either the user who submits the approval request or the owner of the record.


With this approach, the approvers are preselected to some extent. They are not related in any way to any aspect of the record going through the approval aside from being related to the owner or the submitter of the record. And at each step, only one approver (and perhaps their delegated backup) is involved. In many business cases, this approach is much too limiting.

More complex businesses processes

Consider a business process involving a commercial real estate firm. Say a sales representative creates the Opportunity for the sale of a space and submits for approval. Then his manager could handle the first stage of approval and the manager’s supervisor could handle the second stage of approval, with the executive above her handling the final approval. The sales representative, manager, supervisor, and executive are all users and the hierarchical relationships are set up at the beginning.

This works for small organizations where all product lines go through the same approval process, or if there are multiple approval processes that are handled by separate teams all the way through the approval chain.

For instance, Larry the representative sells small spaces. His manager Moe handles the first stage of approval, Moe’s supervisor Cheryl handles the second stage of approval, and Henry (the executive above Cheryl) handles the final approval.

Meanwhile, Jeff the representative sells large spaces with his own hierarchy of approvers, whereas this isn’t possible for Larry. He can’t sell large spaces since Moe is his manager and Moe may only handle small spaces. And even if Moe could handle both, his supervisor Cheryl may not be able to, and on.

Technically, we can get around this by replicating the business process with a separate hierarchical field, but this is clearly counterproductive and won’t scale when the variations start adding up.  Also, if we wanted a team of people, any one of whom (or all of whom) can approve at a particular step, we run into a roadblock.

For any reasonably complex business process, we would need a system where:

  1. The approver is not dependent on the submitter or the owner of the record but on some other aspect(s) of the record (such as the size of the space).
  2. There can be a configurable team of primary approvers.

How can we achieve both?

Fortunately, Salesforce provides an option to automatically assign one or more approvers at each step of the approval process.


Beyond allowing multiple approvers, the key capability is the Related User option. This lists any User Lookup field on the object involved in the approval. We can define one or more Lookup(User) approver custom fields on the object. This allows us to move away from the dependency on manager fields for the submitter or the record owner. But these fields have to dynamically change values at each step so there can be different approvers. This can be achieved with a before update trigger. The trigger code can rely on any aspect of the record and perhaps even a separate approval routing table to drive the whole process. Let’s look at an example.

Problem Statement:

Opportunities for small and large spaces go through the same business process involving a 3-step approval:

  1. Pre-qualification
  2. Revenue study
  3. Proposal readiness

At each step, a separate committee is assigned to approval or rejection. Each committee can have up to 3 members. To solve this problem:

  1. We define custom Lookup(User) fields on the Opportunity object (one for each member of the committee): Opportunity.Approver_1__c, Opportunity.Approver_2__c and Opportunity.Approver_3__c.
  2. We define the approval process on an Opportunity with 3 steps. At each step, we automatically assign approvers to the 3 custom fields defined above on the Opportunity object.
  3. We define a separate approval routing object. Each record is an approval route that has the following fields:
      1. Size of space (small or large)
      2. Committee name
      3. Approver_1__c
      4. Approver_2__c
      5. Approver_3__c


  4. Finally, we write an Opportunity before update trigger that examines the state of the incoming Opportunity and looks up the corresponding approval route and assigns the custom fields on the Opportunity with their equivalents on the approval route. Code shown below:

[snippet caption=”Trigger to auto set approvers based on routes”]

trigger OpportunityTrigger on Opportunity (before insert, before update, before delete, after insert, after update, after delete, after undelete) {

OpportunityTriggerHandler handler = new OpportunityTriggerHandler();

if(Trigger.isBefore && Trigger.isUpdate) {

handler.routeApprovals(Trigger.oldMap, Trigger.newMap);



public with sharing class OpportunityTriggerHandler {

private static final String STAGE_QA = ‘Qualification’;

private static final String STAGE_NA = ‘Needs Analysis’;

private static final String STAGE_VP = ‘Value Proposition’;

private static final String STAGE_PA = ‘Perception Analysis’;

public void routeApprovals(Map<Id, Opportunity> oldOpps,

Map<Id, Opportunity> newOpps) {

List opps_to_route = new List();

Set criteria = new Set();

for(Id oppId : oldOpps.keySet()) {

Opportunity old_opp = oldOpps.get(oppId);

Opportunity new_opp = newOpps.get(oppId);

// Capture every transition for approval routing (even rejections)

if(old_opp.StageName == STAGE_QA && new_opp.StageName == STAGE_NA ||

old_opp.StageName == STAGE_NA && new_opp.StageName == STAGE_VP ||

old_opp.StageName == STAGE_VP && new_opp.StageName == STAGE_PA ||

old_opp.StageName == STAGE_PA && new_opp.StageName == STAGE_VP) {


criteria.add(new_opp.Space_Size__c + ‘:’ + new_opp.Committee__c);



if(!opps_to_route.isEmpty()) {

// fetch the matching routes

List routes = [SELECT Criteria__c,




FROM Approval_Route__c WHERE Criteria__c in :criteria];

Map<String, Approval_Route__c> routesByCriteria = new Map<String, Approval_Route__c>();

for(Approval_Route__c route : routes) {

routesByCriteria.put(route.Criteria__c, route);


for(Opportunity opp : opps_to_route) {

Approval_Route__c route = routesByCriteria.get(opp.Space_Size__c + ‘:’ + opp.Committee__c);

// Cannot leave any approver related-user field empty

if(null != route) {

opp.Approver_1__c = route.Approver_1__c;

opp.Approver_2__c = (null == route.Approver_2__c) ? opp.Approver_1__c : route.Approver_2__c;

opp.Approver_3__c = (null == route.Approver_3__c) ? opp.Approver_2__c : route.Approver_3__c;


else {

// prevent mistaken routes by resetting approvers if route cannot be found.

opp.Approver_1__c = null;

opp.Approver_2__c = null;

opp.Approver_3__c = null;







The salient points to note in the code are:

  1. All steps where approvers change are factored in (both approvals and rejections). We rely on Opportunity.StageName transitions to determine this, but this does not have to be the case in every setup.
  2. All aspects necessary to uniquely locate a route are included. In the example above, we are relying on size of the space in the Opportunity as well as the assigned committee.
  3. All custom Related User fields in the Opportunity are set with a non-null value at each step. This is necessary, or else the approval process will error out. It is okay for the same user (if the others are empty) to be replicated in multiple fields. Salesforce only sends one approval notification to each user.
  4. Approvers are reset when there are problems locating the route. It is better to prevent the approval process from moving forward than to send approval requests to the wrong folks.

With this setup in place, we are able to share the same business process for both small and large spaces with different or shared approvers configured entirely outside of hierarchical relationships with the submitter or the owner of the Opportunity. The approval process is also routed dynamically based on current state of the Opportunity to multiple users.

A sample approval history based on this dynamic setup is shown below.


Previous Article
Chrome Extensions Every Salesforce Developer Should Have
Chrome Extensions Every Salesforce Developer Should Have

By Durgesh Dhoot When I started my development career I usually preferred Firefox over Chrome. But as time ...

Next Article
Tips for Enabling Salesforce Live Agent
Tips for Enabling Salesforce Live Agent

By Urminder Singh Salesforce Live Agent allows your organization to connect with your customers or visitors...