Get Started with Apex Triggers 🎉✨

SalesforceSherpaSalesforceSherpa
10 min read

Crafting Apex Triggers

Apex triggers in Salesforce empower you to execute custom actions before or after events like record insertions, updates, or deletions. Similar to triggers in database systems, Apex triggers offer robust support for record management.

Triggers are ideal for performing operations based on specific conditions, modifying related records, and enforcing operation restrictions.

With triggers, you can leverage the full power of Apex, including executing SOQL and DML queries or invoking custom Apex methods. They serve as a valuable tool for tasks that surpass the capabilities of point-and-click tools, such as validating field values or performing CPU-intensive operations.

Triggers can be defined for various types of objects, both standard and custom, and are automatically triggered by specified database events.

In this blog post, we explore the significance of Apex triggers and how they enhance automation in Salesforce.

Trigger Syntax

The syntax of a trigger definition is different from a class definition’s syntax. A trigger definition starts with the trigger keyword.

It is then followed by the name of the trigger, the Salesforce object that the trigger is associated with, and the conditions under which it fires.

A trigger has the following syntax:

trigger TriggerName on ObjectName (trigger_events) {
   code_block
}

To execute a trigger before or after insert, update, delete, and undelete operations, specify multiple trigger events in a comma-separated list. The events you can specify are:

  • before insert

  • before update

  • before delete

  • after insert

  • after update

  • after delete

  • after undelete

    Trigger Example

    This simple trigger fires before you insert an account and writes a message to the debug log.

    1. In the Developer Console, click File | New | Apex Trigger.

    2. Enter HelloWorldTrigger for the trigger name, and then select Account for the sObject. Click Submit.

    3. Replace the default code with the following.

       trigger HelloWorldTrigger on Account (before insert) {
           System.debug('Hello World!');
       }
      
    4. To save, press Ctrl+S.

    5. To test the trigger, create an account.

      1. Click Debug | Open Execute Anonymous Window.

      2. In the new window, add the following and then click Execute.

         Account a = new Account(Name='Test Trigger');
         insert a;
        
    6. In the debug log, find the Hello World! statement. The log also shows that the trigger has been executed.

Using Context Variables

To access the records that caused the trigger to fire, use context variables. For example, Trigger.new contains all the records that were inserted in insert or update triggers. Trigger.old provides the old version of sObjects before they were updated in update triggers, or a list of deleted sObjects in delete triggers.

Triggers can fire when one record is inserted, or when many records are inserted in bulk via the API or Apex. Therefore, context variables, such as Trigger.new, can contain only one record or multiple records. You can iterate over Trigger.new to get each individual sObject.

This example is a modified version of the HelloWorldTrigger example trigger. It iterates over each account in a for loop and updates the Description field for each.

    trigger HelloWorldTrigger on Account (before insert) {
        for(Account a : Trigger.new) {
            a.Description = 'New description';
        }   
    }

Some other context variables return a Boolean value to indicate whether the trigger was fired due to an update or some other event. These variables are useful when a trigger combines multiple events. For example:

trigger ContextExampleTrigger on Account (before insert, after insert, after delete) {
    if (Trigger.isInsert) {
        if (Trigger.isBefore) {
            // Process before insert
        } else if (Trigger.isAfter) {
            // Process after insert
        }        
    }
    else if (Trigger.isDelete) {
        // Process after delete
    }
}

The following table is a comprehensive list of all context variables available for triggers.

VariableUsage
isExecutingReturns true if the current context for the Apex code is a trigger, not a Visualforce page, a Web service, or an executeanonymous() API call.
isInsertReturns true if this trigger was fired due to an insert operation, from the Salesforce user interface, Apex, or the API.
isUpdateReturns true if this trigger was fired due to an update operation, from the Salesforce user interface, Apex, or the API.
isDeleteReturns true if this trigger was fired due to a delete operation, from the Salesforce user interface, Apex, or the API.
isBeforeReturns true if this trigger was fired before any record was saved.
isAfterReturns true if this trigger was fired after all records were saved.
isUndeleteReturns 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.
newReturns 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. |

Calling a Class Method from a Trigger

You can call public utility methods from a trigger. Calling methods of other classes enables code reuse, reduces the size of your triggers, and improves maintenance of your Apex code. It also allows you to use object-oriented programming.

The following example trigger shows how to call a static method from a trigger. If the trigger was fired because of an insert event, the example calls the static sendMail() method on the EmailManager class. This utility method sends an email to the specified recipient and contains the number of contact records inserted.

  1. In the Developer Console, click File | New | Apex Trigger.

  2. Enter ExampleTrigger for the trigger name, and then select Contact for the sObject. Click Submit.

  3. Replace the default code with the following, and then modify the email address placeholder text in sendMail()to your email address.

     trigger ExampleTrigger on Contact (after insert, after delete) {
         if (Trigger.isInsert) {
             Integer recordCount = Trigger.new.size();
             // Call a utility method from another class
             EmailManager.sendMail('Your email address', 'Trailhead Trigger Tutorial', 
                         recordCount + ' contact(s) were inserted.');
         }
         else if (Trigger.isDelete) {
             // Process after delete
         }
     }
    
  4. To save, press Ctrl+S.

  5. To test the trigger, create a contact.

    1. Click Debug | Open Execute Anonymous Window.

    2. In the new window, add the following and then click Execute.

       Contact c = new Contact(LastName='Test Contact');
       insert c;
      
  6. In the debug log, check that the trigger was fired. Toward the end of the log, find the debug message that was written by the utility method: DEBUG|Email sent successfully

  7. Now check that you received an email with the body text 1 contact(s) were inserted.

    With your new trigger in place, you get an email every time you add one or more contacts!

Triggers are often used to access and manage records related to the records in the trigger context—the records that caused this trigger to fire.

This trigger adds a related opportunity for each new or updated account if no opportunity is already associated with the account. The trigger first performs a SOQL query to get all child opportunities for the accounts that the trigger fired on. Next, the trigger iterates over the list of sObjects in Trigger.new to get each account sObject. If the account doesn’t have any related opportunity sObjects, the for loop creates one. If the trigger created any new opportunities, the final statement inserts them.

  1. Add the following trigger using the Developer Console (follow the steps of the HelloWorldTrigger example but use AddRelatedRecordfor the trigger name).

     trigger AddRelatedRecord on Account(after insert, after update) {
         List<Opportunity> oppList = new List<Opportunity>();
         // Get the related opportunities for the accounts in this trigger
         Map<Id,Account> acctsWithOpps = new Map<Id,Account>(
             [SELECT Id,(SELECT Id FROM Opportunities) FROM Account WHERE Id IN :Trigger.new]);
         // Add an opportunity for each account if it doesn't already have one.
         // Iterate through each account.
         for(Account a : Trigger.new) {
             System.debug('acctsWithOpps.get(a.Id).Opportunities.size()=' + acctsWithOpps.get(a.Id).Opportunities.size());
             // Check if the account already has a related opportunity.
             if (acctsWithOpps.get(a.Id).Opportunities.size() == 0) {
                 // If it doesn't, add a default opportunity
                 oppList.add(new Opportunity(Name=a.Name + ' Opportunity',
                                            StageName='Prospecting',
                                            CloseDate=System.today().addMonths(1),
                                            AccountId=a.Id));
             }           
         }
         if (oppList.size() > 0) {
             insert oppList;
         }
     }
    
  2. To test the trigger, create an account in the Salesforce user interface and name it Apples & Oranges.

  3. In the Opportunities related list on the account’s page, find the new opportunity. The trigger added this opportunity automatically!

    Using Trigger Exceptions

    You sometimes need to add restrictions on certain database operations, such as preventing records from being saved when certain conditions are met. To prevent saving records in a trigger, call the addError() method on the sObject in question. The addError() method throws a fatal error inside a trigger. The error message is displayed in the user interface and is logged.

    The following trigger prevents the deletion of an account if it has related opportunities. By default, deleting an account causes a cascade delete of all its related records. This trigger prevents the cascade delete of opportunities. Try this trigger for yourself! If you’ve executed the previous example, your org has an account called Apples & Oranges with a related opportunity. This example uses that sample account.

    1. Using the Developer Console, add the following trigger.

       trigger AccountDeletion on Account (before delete) {
           // Prevent the deletion of accounts if they have related opportunities.
           for (Account a : [SELECT Id FROM Account
                            WHERE Id IN (SELECT AccountId FROM Opportunity) AND
                            Id IN :Trigger.old]) {
               Trigger.oldMap.get(a.Id).addError(
                   'Cannot delete account with related opportunities.');
           }
       }
      
    2. In the Salesforce user interface, navigate to the Apples & Oranges account’s page and click Delete.

    3. In the confirmation popup, click OK.

      Find the validation error with the custom error message Cannot delete account with related opportunities.

    4. Disable the AccountDeletiontrigger. If you leave this trigger active, you can’t check your challenges.

      1. From Setup, search for Apex Triggers.

      2. On the Apex Triggers page, click Edit next to the AccountDeletion trigger.

      3. Deselect Is Active.

      4. Click Save.

Triggers and Callouts

Apex allows you to make calls to and integrate your Apex code with external Web services. Apex calls to external Web services are referred to as callouts. For example, you can make a callout to a stock quote service to get the latest quotes. When making a callout from a trigger, the callout must be done asynchronously so that the trigger process doesn’t block you from working while waiting for the external service's response. The asynchronous callout is made in a background process, and the response is received when the external service returns it.

To make a callout from a trigger, call a class method that executes asynchronously. Such a method is called a future method and is annotated with @future(callout=true). This example class contains the future method that makes the callout.

ublic class CalloutClass {
    @future(callout=true)
    public static void makeCallout() {
        HttpRequest request = new HttpRequest();
        // Set the endpoint URL.
        String endpoint = 'http://yourHost/yourService';
        request.setEndPoint(endpoint);
        // Set the HTTP verb to GET.
        request.setMethod('GET');
        // Send the HTTP request and get the response.
        HttpResponse response = new HTTP().send(request);
    }
}

This example shows the trigger that calls the method in the class to make a callout asynchronously.

trigger CalloutTrigger on Account (before insert, before update) {
    CalloutClass.makeCallout();
}
0
Subscribe to my newsletter

Read articles from SalesforceSherpa directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

SalesforceSherpa
SalesforceSherpa