
In this post we will learn about writing triggers in Salesforce to fire business logic using Apex. Apex triggers within Salesforce are designed to help you automate certain tasks. Apex triggers allow you to perform custom actions before and after events in Salesforce. These events can include things such as data insertions, updates to existing data, or deletions.
What is Apex Trigger in Salesforce?
Trigger is a procedure in database which automatically invokes whenever a special events in Database occurs. Apex triggers enable you to perform custom actions before or after events to records in Salesforce, such as insertions, updates, or deletions.

When Should we Use Trigger?
There lots of automation tool available in Salesforce. Lets understand the when to use Apex Trigger in Salesforce.
- Complex logic incapable of being processed via declarative artifacts
- Logic associated with DML
- Operations on unrelated objects
- Integration with External Systems
What are different type of Triggers?
There are two types of triggers in Salesforce.
- Before triggers are used to perform a task before a record is inserted or updated or deleted in Salesforce. These 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 set by Salesforce system and to make changes in the other records. The records that fire the after trigger are read-only.
Trigger Events in Salesforce
A trigger is a set of statement which can be executed on the following events. We can add below events with comma-separated. Here is a list of trigger events in salesforce.

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
Variable | Usage |
isExecuting | It tells if code current context is trigger or not |
isInsert | It returns true if trigger fires on insert operation |
isUpdate | It returns true if trigger fires on update operation |
isDelete | It returns true if trigger fires on delete operation |
isBefore | It returns true if code is running in Before context |
isAfter | It returns true if code is running in After context |
new | It holds list of new versions of 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 | It holds new versions of sObject in key value pair This map is only available in before update, after insert, after update, and after undelete triggers |
old | It holds list of old versions of sObject records This sObject list is only available in update and delete 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 corresponding 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 Trigger
Demo 1 : Using context variable in Trigger
Let see the first demo of 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);
}
}
}
}
Recording
Check our our recording for the session.
Trigger Handler pattern
Check this blog post to learn about which all trigger pattern are available in Salesforce. For Trigger Handler code check this post.
Check below ode how to write trigger handler pattern.
Create one Trigger “AccountTrigger“
trigger AccountTrigger on Account( after insert, after update, before insert, before update){
AccountTriggerHandler handler = new AccountTriggerHandler(Trigger.isExecuting, Trigger.size);
if( Trigger.isInsert ){
if(Trigger.isBefore){
handler.OnBeforeInsert(trigger.New);
}
else{
handler.OnAfterInsert(trigger.New);
}
}
else if ( Trigger.isUpdate ){
if(Trigger.isBefore){ handler.OnBeforeUpdate(trigger.New,trigger.Old,Trigger.NewMap,Trigger.OldMap);
}
else{
handler.OnAfterUpdate(trigger.New,trigger.Old,Trigger.NewMap,Trigger.OldMap);
}
}
}
Create one Trigger Handler Class
public with sharing class AccountTriggerHandler
{
private boolean m_isExecuting = false;
private integer BatchSize = 0;
public static boolean IsFromBachJob ;
public static boolean isFromUploadAPI=false;
public AccountTriggerHandler(boolean isExecuting, integer size){
m_isExecuting = isExecuting;
BatchSize = size;
}
public void OnBeforeInsert(List<Account> newAccount){
system.debug('Account Trigger On Before Insert');
}
public void OnAfterInsert(List<Account> newAccount){
system.debug('Account Trigger On After Insert');
}
public void OnAfterUpdate( List<Account> newAccount, List<Account> oldAccount, Map<ID, Account> newAccountMap , Map<ID, Account> oldAccountMap ){
system.debug('Account Trigger On After Update ');
AccountActions.updateContact (newAccount);
}
public void OnBeforeUpdate( List<Account> newAccount, List<Account> oldAccount, Map<ID, Account> newAccountMap , Map<ID, Account> oldAccountMap ){
system.debug('Account Trigger On Before Update ');
}
@future
public static void OnAfterUpdateAsync(Set<ID> newAccountIDs){
}
public boolean IsTriggerContext{
get{ return m_isExecuting;}
}
public boolean IsVisualforcePageContext{
get{ return !IsTriggerContext;}
}
public boolean IsWebServiceContext{
get{ return !IsTriggerContext;}
}
public boolean IsExecuteAnonymousContext{
get{ return !IsTriggerContext;}
}
}
Create one Trigger Action Class
public without sharing class AccountActions{
public static void updateContact ( List<Account> newAccount){
// Add your logic here
}
}
Trigger Best Practice
- 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
- 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.
- Context-Specific Handler Methods : Create context-specific handler methods in Trigger handlers
- Bulkify your Code : Bulkifying Apex code refers to the concept of making sure the code properly handles more than one record at a time.
- 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.
- Using Collections, Streamlining Queries, and Efficient For Loops : It is important to use Apex Collections to efficiently query data and store the data in memory. A combination of using collections and streamlining SOQL queries can substantially help writing efficient Apex code and avoid governor limits.
- 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 the use of internal calls to query and queryMore
- Use @future Appropriately : It is critical to write your Apex code to efficiently handle bulk or many records at a time. This is also true for asynchronous Apex methods (those annotated with the @future keyword). The differences between synchronous and asynchronous Apex can be found
- 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
Trigger Order of Execution
Here is recording of our old session on Order of Execution. Check this blog post to learn more.
Always remember below points before writing trigger :-
1) Order Of Execution
2) Learn about Other Trigger Framework
Assignment
Create Custom object Invoice with field Total Amount Create child object Invoice Line Item with Lookup relationship to Invoice. Add price field into Invoice Line Item. Write trigger on Invoice Line Item which will add some of all child records to parent Invoice |
Further Learning
Trigger Framework
Learn how to completely and cleanly control your Apex code with Trigger Frameworks, why they’re useful, and the various ways to implement them. In this session will talk about Apex Trigger Framework in Salesforce and benefit of using a trigger framework, How many trigger framework are available, which one is lightweight apex trigger framework and Comparison of different approaches.
Don’t forget to register for our next session on Test Classes. Check this post for all other session detail.
Make sure to subscribe our YouTube channel to get notification for video upload. So, learn at your pace and free will and ace your journey to Salesforce!
Summary
I hope this post helped you to understand the Triggers in Salesforce and Best practices of Apex triggers. We tried to covered all about triggers in Apex including type of triggers, Handler pattern and different Triggers frameworks available in market.

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.
Comments(30)
Ankit Desai says:
February 15, 2020 at 7:25 amCompleted Assignment for Day 5
I have created one trigger “InvoiceAmountUpdate” and one class “InvoiceAmountUpdateHandler”
Aathirai says:
February 16, 2020 at 11:59 amCompleted Day 5 Assignment
A Belano says:
February 17, 2020 at 7:16 amDay 5 Assignment Completed
Brisilda says:
February 17, 2020 at 10:24 amEpisode 5 – Demystifying Apex Triggers Assignment Completed!
Archit Dutt Sharma says:
February 17, 2020 at 12:29 pmCompleted Day 5 assignment
Anuj Sahu says:
February 17, 2020 at 12:30 pmDay 5 Assignment is completed
Marishell Cruz says:
February 20, 2020 at 6:48 amcompleted
Amit Chaudhary says:
February 24, 2020 at 10:07 pm8 more to go
Pb says:
March 1, 2020 at 5:45 pmCan someone update all the solution?
Jaya Iyer says:
February 26, 2020 at 3:12 amcompleted assignment 5
Pb says:
March 1, 2020 at 5:46 pmSolution plz
Amit Chaudhary says:
March 4, 2020 at 11:52 pmAfter contest over
Arjinder Kaur says:
March 5, 2020 at 3:30 amassignment 5 completed
Pritam Pramod Dalvi says:
March 12, 2020 at 5:59 pmAssignment 5 Completed
NARESH CHANDA says:
March 13, 2020 at 9:08 pmDay 5 Assignment is completed
ASHOK KUMAR PANIGRAHI says:
April 1, 2020 at 1:16 amDay 5 Assignment is completed
Arjinder Kaur says:
April 7, 2020 at 1:45 amSolution 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;
}
kishore goud says:
April 7, 2020 at 6:12 amHI bro, i have one question, you have declared empty map . since it is empty how you are searching using containskey
Arjinder Kaur says:
April 10, 2020 at 5:38 pmThanks 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!
Arjinder Kaur says:
April 10, 2020 at 9:23 pmCorrections to the code – Please check comments below
kishore goud says:
April 7, 2020 at 6:12 amHI bro, i have one question, you have declared empty map . since it is empty how you are searching using containskey
Arjinder Kaur says:
April 10, 2020 at 6:01 pmFacing 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
Arjinder Kaur says:
April 10, 2020 at 6:07 pmNoticed 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
Ankit Desai says:
April 10, 2020 at 9:02 pmCode 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;
}
}
swathi says:
May 21, 2020 at 5:58 amHi! 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;
}
}
sapna says:
November 18, 2021 at 7:09 amHey, 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??
Jim says:
July 2, 2020 at 4:31 amhello
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
Kiran says:
July 9, 2020 at 6:10 amLogic 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);
}
Amit Chaudhary says:
July 10, 2020 at 3:03 amKeep learning
Sagar says:
February 21, 2021 at 3:14 pmMy 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?