Salesforce Apex Trigger Examples banner

Salesforce Apex triggers provide a powerful way to automate business processes, enforce data validation rules, and maintain data consistency. By leveraging Apex triggers, businesses can streamline their operations, reduce manual effort, and ensure their Salesforce data is accurate and up-to-date.

Apex triggers are used to perform custom actions before or after events to Salesforce records, such as insertions, updates, or deletions. These triggers can handle complex logic, integrate with other systems, and provide real-time responses to changes in data. In this blog, we will explore how real-life business problems can be solved using Salesforce Apex triggers, showcasing the versatility and power of this method.

Table of Contents

Solving Real-Life Scenarios with Salesforce Apex Triggers Example

Ensuring Data Accuracy with Apex Triggers

One of the primary uses of Apex triggers is to enforce data accuracy and validation. For example, companies that rely heavily on phone communication with customers must ensure that all new account records have valid phone numbers before they are inserted into the system. This can be accomplished with a simple Apex trigger that checks the phone number format and prevents the insertion of invalid records. In case you are preparing for an interview check our guide on Salesforce Apex Trigger Scenario Interview Questions.

Automating Business Processes

Apex triggers can also automate various business processes, reducing manual effort and increasing efficiency. For instance, subscription-based businesses can automatically create renewal opportunities when existing opportunities are marked as “Closed Won.” This ensures that renewal processes are initiated without delay, helping maintain continuous service for customers.

Enhancing Customer Communication

Improving customer communication is another area where Apex triggers shine. Businesses can automatically send email notifications to sales managers when high-value deals are created or update contact information across related records to maintain consistency. These automated actions ensure timely and accurate communication with customers, enhancing the overall customer experience.

Maintaining Data Integrity

Preventing the deletion of critical records, such as active subscriptions or key contacts, is vital for maintaining data integrity. Apex triggers can enforce rules that prevent users from deleting records that are essential for business operations, ensuring that crucial data is not lost or compromised.

In complex data models, it is often necessary to synchronize information between related records. Apex triggers can automatically update related records when changes are made to parent records, ensuring that all relevant information is kept consistent and up-to-date. This synchronization is crucial for maintaining a single source of truth across the organization.

1. Validating Phone Numbers

Ensure that all new Account records have a valid phone number before they are inserted into the system.

trigger ValidatePhoneNumber on Account (before insert) {
    for (Account acc : Trigger.new) {
        if (String.isEmpty(acc.Phone) || !Pattern.matches('^\\d{10}$', acc.Phone)) {
            acc.addError('Please enter a valid 10-digit phone number.');
        }
    }
}

2. Auto-Close Cases After 30 Days

Automatically close cases that have been open for more than 30 days.

trigger AutoCloseOldCases on Case (after update) {
    List<Case> casesToClose = new List<Case>();
    for (Case c : Trigger.new) {
        if (c.Status != 'Closed' && Date.today().daysBetween(c.CreatedDate) > 30) {
            c.Status = 'Closed';
            casesToClose.add(c);
        }
    }
    if (!casesToClose.isEmpty()) {
        update casesToClose;
    }
}

3. Sync Contact Address with Account

Sync contact’s mailing address with their parent account’s billing address.

trigger SyncContactAddress on Contact (before insert, before update) {
    for (Contact con : Trigger.new) {
        if (con.AccountId != null) {
            Account acc = [SELECT BillingStreet, BillingCity, BillingState, BillingPostalCode, BillingCountry FROM Account WHERE Id = :con.AccountId];
            con.MailingStreet = acc.BillingStreet;
            con.MailingCity = acc.BillingCity;
            con.MailingState = acc.BillingState;
            con.MailingPostalCode = acc.BillingPostalCode;
            con.MailingCountry = acc.BillingCountry;
        }
    }
}

4. Prevent Deletion of Active Subscriptions

Prevent deletion of subscriptions that are currently active.

trigger PreventActiveSubscriptionDeletion on Subscription__c (before delete) {
    for (Subscription__c sub : Trigger.old) {
        if (sub.Status__c == 'Active') {
            sub.addError('You cannot delete an active subscription.');
        }
    }
}

5. Update Opportunity Close Date

Automatically update the close date of an opportunity to today when the stage is set to “Closed Won.”

trigger UpdateOpportunityCloseDate on Opportunity (before update) {
    for (Opportunity opp : Trigger.new) {
        if (opp.StageName == 'Closed Won') {
            opp.CloseDate = Date.today();
        }
    }
}

6. Create Follow-Up Tasks for New Leads

Create a follow-up task for every new lead created in the system.

trigger CreateFollowUpTask on Lead (after insert) {
    List<Task> tasks = new List<Task>();
    for (Lead lead : Trigger.new) {
        Task t = new Task(
            Subject = 'Follow up with new lead',
            WhatId = lead.Id,
            OwnerId = lead.OwnerId,
            ActivityDate = Date.today().addDays(3),
            Priority = 'High'
        );
        tasks.add(t);
    }
    if (!tasks.isEmpty()) {
        insert tasks;
    }
}

7. Calculate Discount on Opportunity

Automatically calculate a 10% discount on the amount for opportunities with a specific record type.

trigger CalculateDiscount on Opportunity (before insert, before update) {
    for (Opportunity opp : Trigger.new) {
        if (opp.RecordTypeId == '0121N00000XXXXX') { // Replace with the specific record type ID
            opp.Discount__c = opp.Amount * 0.1;
            opp.Total_Amount__c = opp.Amount - opp.Discount__c;
        }
    }
}

8. Prevent Duplicate Contacts

Prevent creating duplicate contacts with the same email address.

trigger PreventDuplicateContacts on Contact (before insert, before update) {
    Set<String> emails = new Set<String>();
    for (Contact con : Trigger.new) {
        emails.add(con.Email);
    }
    List<Contact> existingContacts = [SELECT Email FROM Contact WHERE Email IN :emails];
    for (Contact con : existingContacts) {
        Trigger.newMap.get(con.Id).addError('A contact with this email already exists.');
    }
}

9. Auto-Assign Cases Based on Product

Automatically assign cases to specific teams based on the product.

trigger AutoAssignCasesByProduct on Case (before insert) {
    Map<String, Id> productTeamMap = new Map<String, Id>{
        'Product A' => '0051N00000XXXXX', // User ID for Product A team
        'Product B' => '0051N00000YYYYY'  // User ID for Product B team
    };
    for (Case c : Trigger.new) {
        if (productTeamMap.containsKey(c.Product__c)) {
            c.OwnerId = productTeamMap.get(c.Product__c);
        }
    }
}

10. Update The Contact’s Last Interaction Date

Update the contact’s last interaction date when a task is completed.

trigger UpdateContactLastInteraction on Task (after update) {
    List<Contact> contactsToUpdate = new List<Contact>();
    for (Task t : Trigger.new) {
        if (t.Status == 'Completed' && t.WhatId != null && t.WhatId.getSObjectType() == Contact.SObjectType) {
            Contact con = [SELECT Id, Last_Interaction_Date__c FROM Contact WHERE Id = :t.WhatId];
            con.Last_Interaction_Date__c = Date.today();
            contactsToUpdate.add(con);
        }
    }
    if (!contactsToUpdate.isEmpty()) {
        update contactsToUpdate;
    }
}

11. Limit Opportunity Amount Based on User Role

Limit the maximum opportunity amount based on the user’s role.

trigger LimitOpportunityAmount on Opportunity (before insert, before update) {
    Map<Id, User> userMap = new Map<Id, User>([SELECT Id, UserRole.Name FROM User WHERE Id IN :Trigger.newMap.keySet()]);
    for (Opportunity opp : Trigger.new) {
        User u = userMap.get(opp.OwnerId);
        if (u.UserRole.Name == 'Sales Rep' && opp.Amount > 50000) {
            opp.addError('Sales Reps cannot create opportunities with an amount greater than $50,000.');
        }
    }
}

12. Update Account Type Based on Annual Revenue

Automatically update the account type based on the annual revenue.

trigger UpdateAccountType on Account (before insert, before update) {
    for (Account acc : Trigger.new) {
        if (acc.AnnualRevenue != null) {
            if (acc.AnnualRevenue > 1000000) {
                acc.Type = 'Enterprise';
            } else if (acc.AnnualRevenue > 500000) {
                acc.Type = 'Mid-Market';
            } else {
                acc.Type = 'Small Business';
            }
        }
    }
}

13. Prevent Deletion of Records with Attachments

Prevent deletion of records that have attachments.

trigger PreventDeletionWithAttachments on SObject (before delete) {
    for (SObject s : Trigger.old) {
        Integer attachmentCount = [SELECT COUNT() FROM Attachment WHERE ParentId = :s.Id];
        if (attachmentCount > 0) {
            s.addError('You cannot delete a record with attachments.');
        }
    }
}

14. Create a Case for Expiring Contracts

Create a case for contracts that are expiring within 30 days.

trigger CreateCaseForExpiringContracts on Contract (after update) {
    List<Case> cases = new List<Case>();
    for (Contract c : Trigger.new) {
        if (c.EndDate != null && c.EndDate <= Date.today().addDays(30)) {
            Case newCase = new Case(
                Subject = 'Contract Expiring Soon',
                AccountId = c.AccountId,
                Description = 'The contract ' + c.ContractNumber + ' is expiring on ' + c.EndDate,
                Status = 'New'
            );
            cases.add(newCase);
        }
    }
    if (!cases.isEmpty()) {
        insert cases;
    }
}

15. Auto-Assign Account Managers Based on Industry and Revenue

Assign account managers to accounts based on the industry and annual revenue using custom fields.

trigger AutoAssignAccountManagers on Account (before insert, before update) {
    Map<String, Id> industryManagerMap = new Map<String, Id>{
        'Technology' => '0051N00000XXXXX', // Account Manager for Technology
        'Healthcare' => '0051N00000YYYYY'  // Account Manager for Healthcare
    };

    for (Account acc : Trigger.new) {
        if (acc.Industry != null && acc.AnnualRevenue != null) {
            if (industryManagerMap.containsKey(acc.Industry)) {
                if (acc.AnnualRevenue > 1000000) {
                    acc.Account_Manager__c = industryManagerMap.get(acc.Industry); // Custom field for Account Manager
                }
            }
        }
    }
}

16. Update Opportunity Stage and Send Notification Based on Custom Field

Update the stage of an opportunity and send a notification to the account team when a custom field indicates a significant milestone.

trigger UpdateStageAndNotify on Opportunity (before update) {
    List<Messaging.SingleEmailMessage> emails = new List<Messaging.SingleEmailMessage>();
    for (Opportunity opp : Trigger.new) {
        if (opp.Milestone_Achieved__c && !Trigger.oldMap.get(opp.Id).Milestone_Achieved__c) { // Custom checkbox field
            opp.StageName = 'Value Proposition';
            Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage();
            email.setToAddresses(new String[] {opp.Owner.Email});
            email.setSubject('Milestone Achieved for Opportunity ' + opp.Name);
            email.setPlainTextBody('The opportunity ' + opp.Name + ' has reached a significant milestone.');
            emails.add(email);
        }
    }
    if (!emails.isEmpty()) {
        Messaging.sendEmail(emails);
    }
}

17. Calculate and Update the Custom Field on the Invoice

Automatically calculate the total tax for an invoice based on custom fields and update the total amount.

trigger CalculateTotalTax on Invoice__c (before insert, before update) {
    for (Invoice__c inv : Trigger.new) {
        Decimal totalTax = 0;
        for (Invoice_Line_Item__c lineItem : [SELECT Tax__c FROM Invoice_Line_Item__c WHERE Invoice__c = :inv.Id]) {
            totalTax += lineItem.Tax__c;
        }
        inv.Total_Tax__c = totalTax; // Custom field for total tax
        inv.Total_Amount__c = inv.Subtotal__c + totalTax; // Custom fields for subtotal and total amount
    }
}

Synchronize a custom field value from an account to all related opportunities whenever the field is updated.

trigger SyncCustomFieldToOpportunities on Account (after update) {
    Set<Id> accountIds = new Set<Id>();
    for (Account acc : Trigger.new) {
        if (acc.Custom_Field__c != Trigger.oldMap.get(acc.Id).Custom_Field__c) { // Custom field on Account
            accountIds.add(acc.Id);
        }
    }
    List<Opportunity> oppsToUpdate = [SELECT Id, Custom_Field__c, AccountId FROM Opportunity WHERE AccountId IN :accountIds];
    for (Opportunity opp : oppsToUpdate) {
        opp.Custom_Field__c = Trigger.newMap.get(opp.AccountId).Custom_Field__c; // Custom field on Opportunity
    }
    if (!oppsToUpdate.isEmpty()) {
        update oppsToUpdate;
    }
}

19. Prevent Stage Progression Based on Custom Field Value

Prevent opportunities from moving to the next stage if a custom field is not completed.

trigger PreventStageProgression on Opportunity (before update) {
    for (Opportunity opp : Trigger.new) {
        if (opp.StageName == 'Negotiation/Review' && Trigger.oldMap.get(opp.Id).StageName != 'Negotiation/Review') {
            if (String.isEmpty(opp.Custom_Approval_Status__c)) { // Custom field for approval status
                opp.addError('You cannot move to the Negotiation/Review stage without an approval status.');
            }
        }
    }
}

20. Auto-Calculate Commission on Opportunity Close

Automatically calculate and update the commission amount for a sales rep when an opportunity is closed won.

trigger CalculateCommission on Opportunity (before update) {
    for (Opportunity opp : Trigger.new) {
        if (opp.StageName == 'Closed Won' && Trigger.oldMap.get(opp.Id).StageName != 'Closed Won') {
            Decimal commissionRate = opp.Commission_Rate__c != null ? opp.Commission_Rate__c : 0.05; // Default commission rate
            opp.Commission_Amount__c = opp.Amount * commissionRate; // Custom fields for commission rate and amount
        }
    }
}

21. Send Reminder for Custom Date Field

Send a reminder email 7 days before a custom date field on a contract.

trigger SendReminderForCustomDate on Contract (after insert, after update) {
    List<Messaging.SingleEmailMessage> emails = new List<Messaging.SingleEmailMessage>();
    for (Contract c : Trigger.new) {
        if (c.Custom_Renewal_Date__c != null && Date.today().addDays(7) == c.Custom_Renewal_Date__c) { // Custom date field
            Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage();
            email.setToAddresses(new String[] {c.Owner.Email});
            email.setSubject('Contract Renewal Reminder');
            email.setPlainTextBody('The contract ' + c.ContractNumber + ' is up for renewal on ' + c.Custom_Renewal_Date__c + '.');
            emails.add(email);
        }
    }
    if (!emails.isEmpty()) {
        Messaging.sendEmail(emails);
    }
}

Update all related contacts when a custom field on the account is changed.

trigger UpdateRelatedContacts on Account (after update) {
    Set<Id> accountIds = new Set<Id>();
    for (Account acc : Trigger.new) {
        if (acc.Custom_Status__c != Trigger.oldMap.get(acc.Id).Custom_Status__c) { // Custom field on Account
            accountIds.add(acc.Id);
        }
    }
    List<Contact> contactsToUpdate = [SELECT Id, Custom_Status__c, AccountId FROM Contact WHERE AccountId IN :accountIds];
    for (Contact con : contactsToUpdate) {
        con.Custom_Status__c = Trigger.newMap.get(con.AccountId).Custom_Status__c; // Custom field on Contact
    }
    if (!contactsToUpdate.isEmpty()) {
        update contactsToUpdate;
    }
}

23. Validate Custom Fields Before Insertion

Ensure that certain custom fields are populated before inserting a new record.

trigger ValidateCustomFields on Custom_Object__c (before insert) {
    for (Custom_Object__c obj : Trigger.new) {
        if (String.isEmpty(obj.Custom_Field1__c) || String.isEmpty(obj.Custom_Field2__c)) { // Custom fields
            obj.addError('Custom Field 1 and Custom Field 2 must be populated before inserting the record.');
        }
    }
}

24. Auto-Update Custom Score Field

Automatically update a custom score field based on related records.

trigger UpdateCustomScore on Parent_Object__c (after insert, after update, after delete) {
    Set<Id> parentIds = new Set<Id>();
    if (Trigger.isInsert || Trigger.isUpdate) {
        for (Parent_Object__c parent : Trigger.new) {
            parentIds.add(parent.Id);
        }
    } else if (Trigger.isDelete) {
        for (Parent_Object__c parent : Trigger.old) {
            parentIds.add(parent.Id);
        }
    }
    List<Parent_Object__c> parentsToUpdate = [SELECT Id, Custom_Score__c, (SELECT Score__c FROM Related_Objects__r) FROM Parent_Object__c WHERE Id IN :parentIds];
    for (Parent_Object__c parent : parentsToUpdate) {
        Decimal totalScore = 0;
        for (Related_Object__c related : parent.Related_Objects__r) {
            totalScore += related.Score__c;
        }
        parent.Custom_Score__c = totalScore;
    }
    if (!parentsToUpdate.isEmpty()) {
        update parentsToUpdate;
    }
}

Prevent the deletion of an opportunity if there are related custom records.

trigger PreventOpportunityDeletion on Opportunity (before delete) {
    Set<Id> oppIds = new Set<Id>();
    for (Opportunity opp : Trigger.old) {
        oppIds.add(opp.Id);
    }
    List<Custom_Record__c> relatedRecords = [SELECT Id FROM Custom_Record__c WHERE Opportunity__c IN :oppIds];
    if (!relatedRecords.isEmpty()) {
        for (Opportunity opp : Trigger.old) {
            opp.addError('You cannot delete an opportunity that has related custom records.');
        }
    }
}

26. Create Custom Notifications Based on Field Changes

Create custom notifications when certain fields are changed.

trigger CreateCustomNotifications on Custom_Object__c (after update) {
    List<Custom_Notification__c> notifications = new List<Custom_Notification__c>();
    for (Custom_Object__c obj : Trigger.new) {
        if (obj.Status__c != Trigger.oldMap.get(obj.Id).Status__c) { // Custom field
            Custom_Notification__c notification = new Custom_Notification__c(
                Name = 'Status Changed',
                Related_Record__c = obj.Id,
                Message__c = 'The status has changed to ' + obj.Status__c
            );
            notifications.add(notification);
        }
    }
    if (!notifications.isEmpty()) {
        insert notifications;
    }
}

27. Auto-Populate Custom Lookup Fields

Automatically populate custom lookup fields based on other field values.

trigger AutoPopulateCustomLookup on Custom_Object__c (before insert, before update) {
    Map<String, Id> lookupMap = new Map<String, Id>{
        'Value1' => '0011N00000XXXXX', // Lookup value for Value1
        'Value2' => '0011N00000YYYYY'  // Lookup value for Value2
    };
    for (Custom_Object__c obj : Trigger.new) {
        if (lookupMap.containsKey(obj.Custom_Field__c)) { // Custom field
            obj.Custom_Lookup_Field__c = lookupMap.get(obj.Custom_Field__c); // Custom lookup field
        }
    }
}

28. Prevent Update if Custom Conditions are Met

Prevent updates to records if certain custom conditions are met.

trigger PreventUpdateIfConditionsMet on Custom_Object__c (before update) {
    for (Custom_Object__c obj : Trigger.new) {
        if (obj.Status__c == 'Locked' && obj.Status__c != Trigger.oldMap.get(obj.Id).Status__c) { // Custom field
            obj.addError('You cannot update the record when the status is Locked.');
        }
    }
}

29. Auto-Update Parent Records Based on Child Records

Update parent records when certain custom fields on child records change.

trigger UpdateParentRecords on Child_Object__c (after update) {
    Set<Id> parentIds = new Set<Id>();
    for (Child_Object__c child : Trigger.new) {
        if (child.Custom_Field__c != Trigger.oldMap.get(child.Id).Custom_Field__c) { // Custom field
            parentIds.add(child.Parent_Record__c);
        }
    }
    List<Parent_Object__c> parentsToUpdate = [SELECT Id, Aggregated_Custom_Field__c FROM Parent_Object__c WHERE Id IN :parentIds];
    for (Parent_Object__c parent : parentsToUpdate) {
        Decimal total = 0;
        for (Child_Object__c child : [SELECT Custom_Field__c FROM Child_Object__c WHERE Parent_Record__c = :parent.Id]) {
            total += child.Custom_Field__c;
        }
        parent.Aggregated_Custom_Field__c = total; // Custom field
    }
    if (!parentsToUpdate.isEmpty()) {
        update parentsToUpdate;
    }
}

30. Log Changes to Critical Fields

Log changes to critical fields for auditing purposes.

trigger LogCriticalFieldChanges on Custom_Object__c (after update) {
    List<Change_Log__c> changeLogs = new List<Change_Log__c>();
    for (Custom_Object__c obj : Trigger.new) {
        if (obj.Critical_Field__c != Trigger.oldMap.get(obj.Id).Critical_Field__c) { // Custom field
            Change_Log__c log = new Change_Log__c(
                Related_Record__c = obj.Id,
                Field_Changed__c = 'Critical_Field__c',
                Old_Value__c = String.valueOf(Trigger.oldMap.get(obj.Id).Critical_Field__c),
                New_Value__c = String.valueOf(obj.Critical_Field__c),
                Changed_By__c = UserInfo.getUserId()
            );
            changeLogs.add(log);
        }
    }
    if (!changeLogs.isEmpty()) {
        insert changeLogs;
    }
}

Conclusion

Salesforce Apex triggers are a powerful tool in the database for automating business processes, ensuring data accuracy, and maintaining data integrity. By leveraging the capabilities of Apex triggers, businesses can streamline their operations, reduce manual effort, and enhance the overall quality of their Salesforce data. Whether it’s validating phone numbers, automating renewal opportunities, sending notifications, or preventing critical record deletions, Apex triggers provide a versatile and robust solution to many real-life business challenges.

Implementing Apex triggers requires a good understanding of Salesforce’s data model and business processes. However, the benefits they bring in terms of automation, efficiency, and data integrity make them an invaluable procedure in a database for any organization looking to optimize its Salesforce environment.

Similar Posts

Leave a Reply

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