When an event takes place in Salesforce, it follows an Order of Execution(Described later in this chapter) that calls Triggers at multiple points and is one of the many ways to invoke Apex Code. If we see triggers in a coffee making process, it is doing a set of actions when specific events take place. For example, when we put water in the pot to boil, we usually wait for steam to come out before we pour it out, as if something in the brain goes if steam is coming out, pour water.

To write a trigger, we select File > New > Apex Trigger (instead of Apex Class) in Developer console. The syntax for writing a Trigger is:

trigger TriggerName on Object(triggerEvents){
    //code
}
  • trigger keyword for initializing a Trigger.
  • TriggerName is the name assigned to this Trigger.
  • on keyword that defines what object are we running it on.
  • Object is the name of the object on which the trigger will run.
  • triggerEvents defines when and on what event will the trigger run using the keywords before or after followed by standard DML operations like insert. Multiple trigger events are separated using a ,.

There are two key Trigger class methods to know:

  • Trigger.new contains all the values that are new in the object.
  • Trigger.old contains all the values that were in the object before being updated.

A real-life example is you take a cup. First you fill it with water, then you pour the water out and add milk in it. Trigger.new will return the value milk, and Trigger.old will return the value water when run on the cup. Let’s write a simple trigger on Contact object that checks if the First Name is empty, and fills it as ‘John’ if it is empty.

trigger FirstNamer on Contact(before insert){
    List<Contact> con = Trigger.new;

    for (Contact iterator: con){

        if(iterator.FirstName == null){
            iterator.FirstName = 'John';
        }
    }

}

Now let’s head over to our org and make 2 new entries in the Contact object. The first entry should have First and Last Names and the second should have only a Last Name. You will notice the second record where no First name was specified, the value John has now been filled instead. Let’s write a trigger that makes use of Trigger.old.

This new trigger runs on the Contact object and if the old First Name of a Contact was John, we want to replace whatever new value the user has put in with Smith instead. So if a person with the name John Cool existed in the database and a user tries to update the first name from John to anything else, the value gets saved as Smith instead.

trigger FirstName on Contact(after update){

    List<Contact> con = Trigger.old;
    List<ID> conId = new List<ID>();
    List<Contact> finalList = new List<Contact>();

    for (Contact iterator: con){
        if(iterator.FirstName == 'John'){
            conId.add(iterator.Id);
        }
    }

    List<Contact> updateList = [SELECT ID, FirstName FROM Contact WHERE ID = :conId];

    for (Contact iterator: updateList){
        iterator.FirstName = 'Smith';
        finalList.add(iterator);
    }

    update finalList;

}

While this seems like a lot of code it’s easy to understand with a breakdown:

  • First we make an after update trigger. The reason behind this is Trigger.old doesn’t exist until a change has occurred in the values.
  • We make 3 lists. con to hold old values. conId to hold the IDs of values we need to update. finalList to update the values.
  • The for loop that runs on con goes over the old values and searches for contacts where the First Name was John and stores just he ID in conId list.
  • Now we run a SOQL query to get all the records with matching IDs that currently exist in our database.
  • The for loop runs over the matches and changes their First Name to Smith Cool.

The first question that arises is When to use Before and After Triggers. For this, we need to understand the Order of Execution first.


Order of Execution

Just like the coffee making process which follows a specific sequence, the Order of Execution is a sequence of events that occur when an insert, update or upsert operation occurs in Salesforce. All events in the Order of Execution need to successfully execute before data is committed to the database and if there’s a failure, all changes are rolled back and no further events are executed. Below is the Order of Execution in order of occurrence taken directly from Salesforce Documentation available here

Sequence Event
1 Loads the original record from the database or initializes the record for an upsert statement.
2 Loads the new record field values from the request and overwrites the old values.*
3 Executes flows that make before-save updates.
4 Executes all before triggers.
5 Runs most system validation steps again, such as verifying that all required fields have a non-null value, and runs any user-defined validation rules. The only system validation that Salesforce doesn’t run a second time (when the request comes from a standard UI edit page) is the enforcement of layout-specific rules.
6 Executes duplicate rules. If the duplicate rule identifies the record as a duplicate and uses the block action, the record is not saved and no further steps, such as after triggers and workflow rules, are taken.
7 Saves the record to the database, but doesn’t commit yet.
8 Executes all after triggers.
9 Executes assignment rules.
10 Executes auto-response rules.
11 Executes workflow rules.
12 If there are workflow field updates, updates the record again.
13 If the record was updated with workflow field updates, fires before update triggers and after update triggers one more time (and only one more time), in addition to standard validations. Custom validation rules, flows, duplicate rules, processes, and escalation rules are not run again.
14 Executes processes and flows launched via processes and flow trigger workflow actions. When a process or flow executes a DML operation, the affected record goes through the save procedure.
15 Executes escalation rules.
16 Executes entitlement rules.
17 If the record contains a roll-up summary field or is part of a cross-object workflow, performs calculations and updates the roll-up summary field in the parent record. Parent record goes through save procedure.
18 If the parent record is updated, and a grandparent record contains a roll-up summary field or is part of a cross-object workflow, performs calculations and updates the roll-up summary field in the grandparent record. Grandparent record goes through save procedure.
19 Executes Criteria Based Sharing evaluation.
20 Commits all DML operations to the database.
21 Executes post-commit logic, such as sending email.

Continuation on Sequence 2:

  • If the request came from a standard UI edit page, Salesforce runs system validation to check the record for:
    • Compliance with layout-specific rules
    • Required values at the layout level and field-definition level
    • Valid field formats
    • Maximum field length
  • When the request comes from other sources, such as an Apex application or a SOAP API call, Salesforce validates only the foreign keys. Before executing a trigger, Salesforce verifies that any custom foreign keys do not refer to the object itself.
  • Salesforce runs user-defined validation rules if multiline items were created, such as quote line items and opportunity line items.

The entire Order of Execution can be broken into 3 smaller tables that are easier to understand:

Sequence Event
1 Original record is loaded OR new records are initialized
2 Field values are loaded into sObjects
3 Before triggers are executed
4 System validation rules are run again and custom validation rules are checked
1 Duplicate rules are executed
2 Record is saved but not committed
3 After triggers are executed
4 Assignment rules are executed
5 Auto response rules are executed
6 Before triggers, system validation rules and after triggers are executed due to workflow field updates
1 Processes are executed
2 Escalation rules are executed
3 Entitlement rules are executed
4 Roll-up summary fields and cross object formula fields are updated
5 Updated parent and grand parent records are saved
6 Criteria based sharing rules are evaluated
7 DML operations are committed to the database
8 Post commit logic is executed

So now with the knowledge of Order of Execution the answer to How to decide a Before or After Trigger is simple, always use a before trigger unless there is a need to use components like ID that require an after trigger and this is coming purely from my experience and other developers.

Now that we know how to write a trigger, we need to optimize and follow the 2 key rules:

1 Trigger. 1 Object.

Just how 2 people should not drink from the same cup of coffee at the same time, each object should not have more than 1 trigger. Having more than one trigger will eventually cause problems as the code gets complex, especially in a team environment. This is also to reduce conflicting code and staying within Governor Limits (More on this tomorrow).

Trigger Helper Class

Writing all of the code in the trigger itself results in bigger, complex looking files that may later cause issues. To avoid this we call a class and pass values to it instead, and call it a Helper Class. Think of it this way, you can directly drink your coffee from the french press or even the pot but a cup reduces the mess of drinking the coffee.

Let’s rewrite our FirstNamer trigger to use a helper class.

FirstNamer Trigger File

trigger FirstName on Contact(after update){
    FirstNameHelper.replaceName(trigger.old);
}

FirstNamer Class File

public class FirstNameHelper{

    public static void replaceName(List<Contact> con){
        List<ID> conId = new List<ID>();
        List<Contact> finalList = new List<Contact>();

        for (Contact iterator: con){
            if(iterator.FirstName == 'John'){
                conId.add(iterator.Id);
            }
        }

        List<Contact> updateList = [SELECT ID, FirstName FROM Contact WHERE ID = :conId];

        for (Contact iterator: updateList){
            iterator.FirstName = 'Smith';
            finalList.add(iterator);
        }

        update finalList;
    }
}

While most of our code remains the same, we made one big change. In our trigger class, we pass the trigger.old as an input parameter to our helper class, that has a method called replaceName that takes a list of contacts as an input. We know trigger.old will be a list of contacts because we are running it within the context of the Contact object,trigger FirstName on Contact. But what if we want to execute different helper classes at different times?

Let’s say in our example, we have a trigger called NameReplacer that runs on the Contact Object and runs at two different times. When it’s before insert, we want to run a method firstName() that replaces first name, and during the after update we want to run a method lastName() that replaces last name. We use Trigger Context Variables to achieve this.

Trigger Context Variables

All triggers allow the developer to access run time context, which roughly means we get to define what processes run at what time. This is available in methods of Trigger class, available here in the official documentation:

Variable Usage
isExecuting Returns true if the current context for the Apex code is a trigger, not a Visualforce page, a Web service, or an executeanonymous() API call.
isInsert Returns true if this trigger was fired due to an insert operation, from the Salesforce user interface, Apex, or the API.
isUpdate Returns true if this trigger was fired due to an update operation, from the Salesforce user interface, Apex, or the API.
isDelete Returns true if this trigger was fired due to a delete operation, from the Salesforce user interface, Apex, or the API.
isBefore Returns true if this trigger was fired before any record was saved.
isAfter Returns true if this trigger was fired after all records were saved.
isUndelete Returns true if this trigger was fired after a record is recovered from the Recycle Bin. This recovery can occur after an undelete operation from the Salesforce user interface, Apex, or the API.
new Returns a list of the new versions of the sObject records. This sObject list is only available in insert, update, and undelete triggers, and the records can only be modified in before triggers.
newMap A map of IDs to the new versions of the sObject records. This map is only available in before update, after insert, after update, and after undelete triggers.
old Returns a list of the old versions of the sObject records. This sObject list is only available in update and delete triggers.
oldMap A map of IDs to the old versions of the sObject records. This map is only available in update and delete triggers.
operationType Returns an enum of type System.TriggerOperation corresponding to the current operation.Possible values of the System.TriggerOperation enum are: BEFORE_INSERT, BEFORE_UPDATE, BEFORE_DELETE,AFTER_INSERT, AFTER_UPDATE, AFTER_DELETE, and AFTER_UNDELETE. If you vary your programming logic based on different trigger types, consider using the switch statement with different permutations of unique trigger execution enum states.
size The total number of records in a trigger invocation, both old and new.

Let’s write our NameReplacer trigger:

trigger NameReplacer on Contact(before insert, after update){
    if (Trigger.isBefore && Trigger.isInsert){
        firstName();
    }
    if (Trigger.isAfter && Trigger.isUpdate){
        lastName();
    }
}

Alternatively, this can be written in nested if statements:

trigger NameReplacer on Contact(before insert, after update){
    if (Trigger.isBefore){
        if(Trigger.isInsert){
            firstName();
        }
    }

    if (Trigger.isAfter){
        if(Trigger.isUpdate){
            lastName();
        }
    }
}

Either ways works and is on the developer to choose which format they prefer. Personally, I prefer nesting inside if statements to keep the file easily readable. We will look into remaining Trigger Context Variables like oldMap and newMap in the next chapter.

The next question that comes instantly is Why to write triggers when Flows/Process Builder/Workflow can be used instead?. A very simple answer for functionalities that overlap (like updating a record), is purely on what you prefer. If I am working on an org and I need to update contacts based on a criteria, I will write a trigger and helper class because I am comfortable with it, but my colleague who is an Administrator might end up making a Flow that does the same job. Apart from preferance, Triggers actually excel when it comes to testing more complex automations that can be done using flows, and require less computing power to execute making them faster. Where triggers, or writing Apex in general lacks behind is the fact that you cannot write your code in production. All Apex code needs to be in a sandbox environment and needs to pass 75% code coverage to be moved into production.

We look more into Apex Test Classes and other techincal requirements later in the course.

Summary

  • Triggers run when a record is manipulated.
  • Triggers should be written with a helper class.
  • Trigger Context Variables let you access run time context.
  • Order of Execution is how a record goes from text on screen to be saved in the database.
  • Use a before trigger by default and after trigger when usage of ID is required.
Day 12: Trigger.oldMap and Trigger.newMap