Apex Triggers in Salesforce: A Complete Guide

This post will teach us how to write Apex triggers in Salesforce to fire business logic. Apex triggers in Salesforce are designed to help you automate specific tasks. It allows you to perform custom actions before and after events in Salesforce. These events can include data insertions, updates to existing data, or deletions. Let’s start our Apex Triggers in Salesforce: A Complete Guide.

What are Apex Triggers in Salesforce?

Salesforce Apex Trigger is a procedure in a database that is automatically invoked whenever a special event in the database occurs. Triggers enable you to perform custom actions before or after events to records in Salesforce, such as insertions, updates, or deletions.

Apex Triggers in Salesforce
Apex Triggers

When should we use Apex Trigger in Salesforce?

There are lots of automation tools available in Salesforce. Let’s understand when to use Apex Trigger in Salesforce.

  1. Complex logic incapable of being processed via declarative artifacts
  2. Logic associated with DML
  3. Operations on unrelated objects
  4. Integration with External Systems

Types of Apex Triggers in Salesforce

There are two types of triggers in Salesforce.

  • Before triggers are used to perform a task before a record is inserted, updated, or deleted in Salesforce, Triggers are used to update or validate record values before they are saved to the database.
  • After triggers are used, if we want to use the information the Salesforce system sets and make changes in the other records. The records that fire the after trigger are read-only.

Trigger Events in Salesforce

An Apex trigger is a set of statements that can be executed on the following events. We can add the events below with commas separated. Here is a list of trigger events in Salesforce.

Trigger Events in Salesforce

Trigger Syntax

trigger <TriggerName> on <ObjectName> (trigger_events) {
// Logic
}

Apex Trigger Context Variable

All triggers define implicit variables that allow developers to access run-time context. These variables are contained in the System.Trigger class.

Apex Trigger Context Variable list

Variable Usage
isExecuting It tells if the code’s current context is triggered or not.
isInsert It returns true if the trigger fires on the insert operation.
isUpdate It returns true if the trigger fires on the update operation.
isDelete It returns true if the trigger fires on the delete operation.
isBefore It returns true if the code is running in the Before context.
isAfter It returns true if the code is running in the After context.
new Returns a list of the new versions of the sObject records.
newMap It holds new versions of sObject in key-value pair. This map is only available before the update, after insert, after the update, and after undelete triggers.
old It holds a list of old versions of sObject records. This sObject list is only available in update and deletes triggers.
oldMap It holds old versions of sObject in key-value pair. This map is only available in update and delete triggers.
operationType Returns an enum of type System.TriggerOperation corresponds to the current operation. System.TriggerOperation enum are: BEFORE_INSERT, BEFORE_UPDATE, BEFORE_DELETE,AFTER_INSERT, AFTER_UPDATE, AFTER_DELETE, and AFTER_UNDELETE.
size The total number of records in a trigger invocation, both old and new.

Example of Apex Triggers in Salesforce

Let’s see the example of Apex Triggers in Salesforce. Using context variable in Trigger. Let’s see the first demo of the context variable in Trigger.

trigger AccountTrigger on Account (before insert, before update, after update) {
    if(Trigger.isBefore && Trigger.isInsert){
        System.debug('I am in AccountTrigger before insert context');
    }
    if(Trigger.isUpdate){
        if(Trigger.isBefore){
            for(Account acc :Trigger.new){
                System.debug('New Name'+ acc.Name);
                System.debug('Old Name'+ Trigger.oldMap.get(acc.Id).Name);
            }
        }
    }
}

Apex Triggers in Salesforce Video

Check our recording for the session.

YouTube video

Trigger Handler Pattern

The basic idea behind the Trigger Handler pattern is to separate the trigger logic into a separate class from the trigger itself. The trigger class is responsible for registering the trigger with the appropriate events (such as before insert or after update), and for delegating the actual logic to the handler class.

Trigger Handler pattern
Trigger Handler Pattern

Learn more about the Trigger Handler pattern in Salesforce.

Apex Trigger Best Practice in Salesforce

Here is a list of all Apex trigger best practices we should use while creating the trigger in Salesforce.

1. One Trigger Per Object

A single Apex Trigger is all you need for one particular object. If you develop multiple Triggers for a single object, you have no way of controlling the order of execution if those Triggers can run in the same contexts.

2. Logic-less Triggers

If you write methods in your Triggers, those can’t be exposed for test purposes. You also can’t expose logic to be re-used anywhere else in your org.

3. Context-Specific Handler Methods

Create context-specific handler methods in Trigger handlers.

4. Bulkify your Code

Bulkifying Apex code refers to ensuring the code properly handles more than one record at a time.

5. Avoid SOQL Queries or DML statements inside FOR Loops

An individual Apex request gets a maximum of 100 SOQL queries before exceeding that governor limit. So if this trigger is invoked by a batch of more than 100 Account records, the governor limit will throw a runtime exception

6. Using Collections, Streamlining Queries, and Efficient For Loops

It is important to use Apex Collections to query data and store the data in memory efficiently. A combination of using collections and streamlining SOQL queries can substantially help write efficient Apex code and avoid governor limits.

7. Querying Large Data Sets

The total number of records that can be returned by SOQL queries in a request is 50,000. If returning a large set of queries causes you to exceed your heap limit, then a SOQL query for loop must be used instead. It can process multiple batches of records through internal calls to query and query more.

8. Use @future Appropriately

Writing your Apex code to handle bulk or many records at a time efficiently is critical. This is also true for asynchronous Apex methods (those annotated with the @future keyword). The differences between synchronous and asynchronous Apex can be found

9. Avoid Hardcoding IDs

When deploying Apex code between sandbox and production environments or installing Force.com AppExchange packages, it is essential to avoid hardcoding IDs in the Apex code. By doing so, if the record IDs change between environments, the logic can dynamically identify the proper data to operate against and not fail.

How to avoid recursion in Trigger

Learn about different ways to avoid recursion in Trigger. Learn why and which one we should use.

  1. Use Static Boolean Variable
  2. Use Static Set to Store Record Id.
  3. Use Static Map
  4. Use Old Map
  5. Follow Best practices for triggers

Trigger Order of Execution

Here is a recording of our old session on Order of Execution. Check this blog post to learn more.

YouTube video

Trigger Assignment

-Create a Custom object Invoice with the field Total Amount.
-Create child object Invoice Line Item with Lookup relationship to Invoice.
-Add the price field to the invoice line item.
-Write a trigger on the Invoice Line Item, which will add some of all child records to the parent Invoice.

Trigger Framework in Salesforce

Learn how to completely and cleanly control your Apex code with Trigger Frameworks, why they’re useful, and the various ways to implement them. This session will talk about Apex Trigger Framework in Salesforce and the benefits of using a trigger framework, How many trigger frameworks are available, which one is a lightweight apex trigger framework, and a Comparison of different approaches.

Check this post for all other session details. Also, check our Salesforce Interview Questions on Triggers.

Triggers FAQ’s

How to create Apex Trigger in Salesforce?

You can create triggers in Salesforce using the Developer console, VSCode, or any Developer tools. Here is a step to create Apex Trigger: Click on Setup → Build → Develop → Click ‘Apex Class’ → Click On “New” button → “ Add Code for Apex Class” → Click on “Save”.

What do you mean by the bulkifying trigger

Bulkifying means handing more than one record in triggers. As per best practice, your trigger should be bulkified.

How do I call an Apex trigger in Salesforce?

Apex trigger automatically called any DML performed on SObject in Salesforce.

How many types of triggers are there in Salesforce

There are two types of apex triggers we have in Salesforce. 1. Before triggers and 2. After triggers.

Related Post

Summary

I hope this post helped you understand the Apex triggers in Salesforce and the best practices of Apex triggers. We tried to cover all about triggers in Apex, including the type of triggers, Handler pattern, and different Trigger frameworks available in the market. Hope this Apex Triggers in Salesforce: A Complete Guide will help you.

Amit Chaudhary
Amit Chaudhary

Amit Chaudhary is Salesforce Application & System Architect and working on Salesforce Platform since 2010. He is Salesforce MVP since 2017 and have 17 Salesforce Certificates.

He is a active blogger and founder of Apex Hours.

Articles: 469

40 Comments

  1. Completed Assignment for Day 5
    I have created one trigger “InvoiceAmountUpdate” and one class “InvoiceAmountUpdateHandler”

  2. Solution for Assignment : Trigger on Invoice Line Item which will add sum of all child records to parent Invoice:

    trigger AddInvoiceItems on Invoice_Line_Item__c ( after insert, after update, after delete ) {

    List ListIds = new List();

    if ( Trigger.isInsert || Trigger.isUpdate ) {

    for ( Invoice_Line_Item__c item : Trigger.New ) {
    if ( Trigger.isInsert || ( Trigger.isUpdate && Trigger.OldMap.get(item.ID).price__c!=item.price__c) ) {

    listIds.add(item.Invoice__c);

    }
    }
    }

    if(Trigger.isDelete)
    {
    for( Invoice_Line_Item__c item : Trigger.Old ) {

    listIds.add(item.Invoice__c);

    }
    }

    Map TotalPriceMap = new Map();

    for ( Invoice_Line_Item__c item : [ SELECT Id, price__c, invoice__c FROM Invoice_Line_Item__c WHERE invoice__c IN : ListIds ] ) {

    Decimal totalPrice = 0;
    if( TotalPriceMap.containsKey ( item.invoice__c ) )
    totalPrice = TotalPriceMap.get( item.invoice__c );
    totalPrice = totalPrice + item.price__c;
    TotalPriceMap.put(item.invoice__c,totalPrice);
    }

    List updateInvoice = new List();
    for ( Id invoiceId : TotalPriceMap.KeySet() ) {
    Invoice__c inv = new Invoice__c ( Id = invoiceId );
    inv.Total_Amount__c = TotalPriceMap.get( invoiceId );
    updateInvoice.add(inv);
    }

    if ( updateInvoice.size() > 0 )
    Update updateInvoice;

    }

    • HI bro, i have one question, you have declared empty map . since it is empty how you are searching using containskey

      • Thanks for pointing out. It should be like this:

        Map TotalPriceMap = new Map();

        I believe, datatype got missed while I pasted my solution.
        I am sorry about it. I will check and correct it.

        Thanks again!

    • trigger UpdateInvoice on InvoiceLineItem__c (before insert,before update,before delete,after insert, after update, after delete, after undelete)
      {
      List ListIds = new List();

      if ( Trigger.isInsert || Trigger.isUpdate ) {

      for ( InvoiceLineItem__c item : Trigger.New ) {
      if ( Trigger.isInsert || ( Trigger.isUpdate && Trigger.OldMap.get(item.ID).price__c!=item.price__c) ) {

      listIds.add(item.Invoice__c);

      }
      }
      }

      if(Trigger.isDelete)
      {
      for( InvoiceLineItem__c item : Trigger.Old ) {

      listIds.add(item.Invoice__c);

      }
      }

      Map TotalPriceMap = new Map();

      for ( InvoiceLineItem__c item : [ SELECT Id, price__c, invoice__c FROM InvoiceLineItem__c WHERE invoice__c IN : ListIds ] ) {

      Decimal totalPrice = 0;
      if( TotalPriceMap.containsKey ( item.invoice__c ) )
      totalPrice = TotalPriceMap.get( item.invoice__c );
      totalPrice = totalPrice + item.price__c;
      TotalPriceMap.put(item.invoice__c,totalPrice);
      }

      List updateInvoice = new List();
      for ( Id invoiceId : TotalPriceMap.KeySet() ) {
      Invoice__c inv = new Invoice__c ( Id = invoiceId );
      inv.TotalAmount__c = TotalPriceMap.get( invoiceId );
      updateInvoice.add(inv);
      }

      if ( updateInvoice.size() > 0 )
      Update updateInvoice;
      }

  3. HI bro, i have one question, you have declared empty map . since it is empty how you are searching using containskey

    • Facing some issue with code within angle brackets after I posted comment.

      Correction:
      List ListIds = new List(); // Id within angle brackets after List
      Map TotalPriceMap = new Map(); // Id,Decimal within angle brackets after Map
      List updateInvoice = new List(); // Invoice__c within angle brackets after List

  4. Noticed some issues with posted code : I noticed code(data type) within angle brackets got removed after I posted it. I am sorry about this.
    Please add following datatype ( written after backslash ) within angle bracket after List or Map

    Correction:

    List ListIds = new List(); // Add Id within angle brackets after List

    Map TotalPriceMap = new Map(); // Add Id , Decimal within angle brackets after Map

    List updateInvoice = new List(); // Add Invoice__c within angle brackets after List

  5. Code for Assignment Day 5
    ——————————–

    trigger InvoiceAmountUpdate on Invoice_Line_Item__c (after insert,
    after update,
    after delete) {

    String invoiceId = ”;
    if(Trigger.isInsert || Trigger.isUpdate) {
    for(Invoice_Line_Item__c invoiceLineItem : Trigger.new) {
    if(invoiceLineItem.Invoice__c != null) {
    invoiceId = invoiceLineItem.Invoice__c;
    }
    }
    }
    if(Trigger.isDelete){
    for(Invoice_Line_Item__c invoiceLineItem : Trigger.old) {
    if(invoiceLineItem.Invoice__c != null) {
    invoiceId = invoiceLineItem.Invoice__c;
    }
    }
    }
    if(invoiceId != ”) {
    InvoiceAmountUpdateHandler.updateInvoiceAmount(invoiceId);
    }
    }

    —————————————————————————–

    public class InvoiceAmountUpdateHandler {
    public static void updateInvoiceAmount(String invoiceId) {
    List items = new List();
    items = [select Id,price__c from Invoice_Line_Item__c where Invoice__c = :invoiceId];
    Decimal amount = 0;
    for(Invoice_Line_Item__c item : items) {
    amount = amount + item.Price__c;
    }
    Invoice__c invoice = new Invoice__c();
    invoice.Id = invoiceId;
    invoice.Total_Amount__c = amount;
    update invoice;

    }
    }

    • Hi! I have written a code for trigger assignment task and its working well for insert and update. Can you please tell me whether I need to modify my code for better results..

      Parent: Invoice__c
      Child: Invoice_Line_Item__c

      Trigger code:
      trigger Invoice_Line_Item__cTrigger on Invoice_Line_Item__c (after insert, after Update) {

      if((trigger.IsInsert == true && Trigger.Isafter == true) ||(trigger.IsUpdate == true && Trigger.Isafter == true)){

      Invoice_Line_ItemHandler.AmoutUpd(trigger.new);
      }
      }

      APEXHANDLER::

      public class Invoice_Line_ItemHandler {

      public static list P_id = new list();
      public static decimal amount=0;

      public static void AmoutUpd(list ChildRec){

      for(Student__c Pid : StuRec){

      if(pid.Teacher_name__c != null){

      P_id.add(pid.Invoice_name__c);
      }
      }
      list Rec = [SELECT Total_amout__c,name,(SELECT Invoice_name__c,price__c FROM Invoice_Line_Item__r) FROM Invoice__c WHERE ID IN :P_id];

      for(Invoice__c a: Rec){

      for(Invoice_Line_Item__c b: a.Invoice_Line_Item__r){

      amount = amount+ b.price__c ;
      }

      a.Total_amout__c = amount;
      }
      update Rec;
      }

      }

    • Hey, if we want to delete some record from Invoice line item and again add the record in Invoice line item and i have created one more filed at Invoice line item that is count of line Item in particular Invoice then count should be increase… how to do it??

  6. hello
    I copied the Handler Class from above into the Dev console but I receive Unexpected token ‘List’. error for this statement:
    List items = new List();

    What am i missing here?

    -jim

  7. Logic for Trigger
    ================
    public class ApexHourTriggerAssignment {

    public static void updateInvoceObjectField(List lstinvln){
    Decimal amount=0;
    Set liStIds = new Set();
    for (Invoice_Line_Up__c ilu :lstinvln) {
    if(ilu.Invoice_Name__c != null) {
    liStIds.add(ilu.Invoice_Name__c);
    }
    }
    List ListInvoice = [Select id, Total_Amount__c, (select id, price__c from Invoices_Line_up__r)
    from Invoice__c Where Id IN :liStIds];
    ListListInv = new List();
    For(Invoice__c inv : ListInvoice){
    for(Invoice_Line_Up__c ilu1 : inv.Invoices_Line_up__r){
    amount = amount + ilu1.price__c;
    }
    inv.Total_Amount__c = amount;
    ListInv.add(inv);
    }
    update ListInv;
    }
    }

    ======================
    trigger ApexHourTriggerAssignmentTrigger on Invoice_Line_Up__c (After insert,After Update) {

    ApexHourTriggerAssignment.updateInvoceObjectField(Trigger.new);
    }

  8. My solution for assignment.

    trigger TotalInvoiceAmount on Invoice_Line_Item__c (after insert) {

    Map UniqueInvoiceToUpdate = new map(); // To maintain unique sobject to be updated
    Map TotalAMount = new Map(); // To maintain the updated amount of each invoice sobject

    ListInvoiceToUpdate = new List(); // final list to perform dml update operation
    Invoice__c ParentInvoice;

    for (Invoice_Line_Item__c currentInvoiceItem : Trigger.New) {

    ParentInvoice = [select id, Total_Amount__c from Invoice__c where Id = :currentInvoiceItem.Invoice__c LIMIT 1][0];

    // Check if we have updated total value of invoice in previous iterations:
    if (TotalAmount.get(ParentInvoice.Id) != null) {

    // if yes, add the current amount of invoice line item to the previous updated invoice object.
    // Not that, we have not commit to database yet. Hence we are retrieving updated amount from map and not from sobject’s Total amount field
    ParentInvoice.Total_Amount__c = TotalAmount.get(ParentInvoice.Id) + currentInvoiceItem.Amount__c;
    }
    else {
    // We haven’t saved total amount in map yet.
    // Add current invoice line item amount to total amount of invoice object.
    ParentInvoice.Total_Amount__c += currentInvoiceItem.Amount__c;
    }
    // save updated total amount of invoice object in map
    TotalAmount.put(ParentInvoice.Id, ParentInvoice.Total_Amount__c );

    // update invoice object’s map.
    UniqueInvoiceToUpdate.put(ParentInvoice.Id, ParentInvoice);
    }

    // iterate UniqueInvoiceToUpdate map to put those invoice objects in list.
    for (Invoice__c I : UniqueInvoiceToUpdate.values()){
    InvoiceToUpdate.add(I);
    }

    // update list.
    update InvoiceToUpdate;
    }

    Any better solution?

  9. Hello there, I have clarification on assignment –
    i)create custom object invoice with field total amount
    ii)create child object invoice line item with lookup relationship to invoice
    iii)add price field into invoice line item
    ​​​​​​​
    I have created ‘invoice’ object and field ‘total amount’
    now how to create lookup relation on child object
    I’m confused whether we can create relationship on object, it can be created on fields only as I know or maybe something I don’t know on this.
    can anyone plz guide.

  10. Apex class
    —————
    public class AddTotalAmountOnInvoice {

    public static void addInvoiceLineItemTotalAmount(List slist){
    List invoiceId=new List();
    for(Invoice_Line_Item__c ids:slist){
    invoiceId.add(ids.Invoice__c);
    }
    Map addAmountList = new Map();
    for(Invoice_Line_Item__c inv:[SELECT Id, Name,price__c ,Invoice__c FROM Invoice_Line_Item__c WHERE Invoice__c=:invoiceId]){
    Decimal num=inv.price__c;
    if(!addAmountList.containsKey(inv.Invoice__c)){
    addAmountList.put(inv.Invoice__c,num);
    }
    else{
    Decimal num2=addAmountList.get(inv.Invoice__c)+num;
    addAmountList.put(inv.Invoice__c,num2);
    }
    }
    System.debug(‘invocie:’+addAmountList);
    List amountList = new List();
    for(Invoice__c invc:[SELECT Id, Total_Amount__c FROM Invoice__c WHERE ID IN:addAmountList.KeySet()]){
    if(addAmountList.containsKey(invc.Id)){
    invc.Total_Amount__c = addAmountList.get(invc.Id);
    amountList.add(invc);
    }
    }
    if(amountList!=NULL && amountList.size()>0){
    update amountList;
    }
    }
    }
    ——————–
    Trigger class
    —————
    trigger TotalAmount on Invoice_Line_Item__c (before insert,before update,before delete,after insert, after update, after delete, after undelete) {
    if(Trigger.isBefore){
    if(Trigger.isInsert){
    AddTotalAmountOnInvoice.addInvoiceLineItemTotalAmount(Trigger.new);
    }
    if(Trigger.isDelete){
    AddTotalAmountOnInvoice.addInvoiceLineItemTotalAmount(Trigger.new);
    }
    }
    if(Trigger.isAfter){
    if(Trigger.isUpdate){
    AddTotalAmountOnInvoice.addInvoiceLineItemTotalAmount(Trigger.new);
    }
    if(Trigger.isInsert){
    AddTotalAmountOnInvoice.addInvoiceLineItemTotalAmount(Trigger.new);
    }
    if(Trigger.isDelete){
    AddTotalAmountOnInvoice.addInvoiceLineItemTotalAmount(Trigger.new);
    }
    }
    }

  11. Context Variable has some mistakes
    example
    1.
    isInsert It tells if the code’s current context is triggered or not.
    2.isExecuting It returns true if the trigger fires on the update operation.
    3.isUpdate It returns true if the trigger fires on the delete operation.
    4. isDelete It returns true if the code is running in the Before context.
    5. isBefore It returns true if the code is running in the After context.

    and many more,
    Can you please look at this blog, I think some ups and down,

  12. Context Variable has some issue
    example
    1.
    isInsert It tells if the code’s current context is triggered or not.
    2.isExecuting It returns true if the trigger fires on the update operation.
    3.isUpdate It returns true if the trigger fires on the delete operation.
    4. isDelete It returns true if the code is running in the Before context.
    5. isBefore It returns true if the code is running in the After context.

    and many more,
    Can you please look at this blog, I think some ups and down,

Leave a Reply

Your email address will not be published. Required fields are marked *