Skip to main content

#Triggers0 discussing

🔥 Salesforce Apex & Trigger Best Practices

Writing Apex and Triggers the right way makes your org scalable, maintainable, and future-ready 🚀

✅ Apex Best Practices

  • Bulkify your code (always handle multiple records)
  • Avoid SOQL/DML inside loops
  • Use collections (Set, Map, List) efficiently
  • Follow proper exception handling
  • Write meaningful test classes (aim for quality, not just coverage)
  • Use @future / Queueable for async processing

⚡ Trigger Best Practices

  • One trigger per object
  • Keep triggers logic-free (use Trigger Handlers)
  • Use context variables wisely (before/after, insert/update/delete)
  • Prevent recursion using static variables
  • Make triggers bulk-safe & reusable

👉 Clean code today = Fewer issues tomorrow

 

🔥 Salesforce Apex & Trigger Best PracticesWriting Apex and Triggers the right way makes your org scalable, maintainable, and future-ready 🚀✅ Apex Best PracticesBulkify your code (always handle multi

 

#Salesforce Developer #Apex #Triggers#Salesforce #SFDC Admin Certification #TrailblazerCommunityHelp #Trailhead Challenges #Agentforce

0/9000

I wrote trigger on Account object, When I test the Trigger it was working fine when I entered the the record manually., But Trigger is not fired when the data is coming through integration even it passing all the criteria. Please help me why the trigger is not firing

. #BatchClass, #Batchapex, #Triggers,@developers

7 answers
  1. Jul 22, 2025, 10:57 AM

    Hi Naveen, 

     

    Check the following things, 

     

    API User Permissions: Verify that the integration user has the necessary permissions to trigger the logic. 

    Integration Method: Ensure the API method used (like create, update) aligns with your trigger logic. 

     

    please mark this answer helpful if it solved your query.

     

     

     

     

     

    Arun Goel

    Oct 1, 2024, 7:40 PM

     

     

     

    Hi Naveen, 

      

    Check the following things, 

      

    API User Permissions: Verify that the integration user has the necessary permissions to trigger the logic. 

    Integration Method: Ensure the API method used (like create, update) aligns with your trigger logic. 

      

    please mark this answer helpful if it solved your query.

     

     

     

     

     

    Arun Goel

    Oct 1, 2024, 7:40 PM

     

     

     

    Hi Naveen, 

      

    Check the following things, 

      

    API User Permissions: Verify that the integration user has the necessary permissions to trigger the logic. 

    Integration Method: Ensure the API method used (like create, update) aligns with your trigger logic. 

      

    please mark this answer helpful if it solved your query.

0/9000

Create a trigger that calculates and updates a custom field on the Account object named TotalOpportunityValue whenever an Opportunity related to that Account is created, updated, or deleted. The field should represent the sum of the Amount field of all related Opportunities.

 

#Triggers

5 answers
  1. Jan 13, 2025, 10:56 AM

    Hello @Swati Patle

     

    You can take reference from below code.

     

    Trigger TotalOpportunityAmount on Opportunity (after insert,after update,after delete,after undelete ) {

    List<Account> oppListToIterate = Trigger.isDelete ? Trigger.old : Trigger.New;

    List<Account> accountListToUpdate = new List<Account>();

    set<Id> accountIds=new set<Id>();

    for(Opportunity opp:oppListToIterate){

    if((Trigger.isInsert || Trigger.isDelete) && opp.ExpectedRevenue != null )

    accountIds.add(opp.accountID);

    else if(Trigger.isUpdate && (opp.ExpectedRevenue != Trigger.oldMap.get(opp.Id).ExpectedRevenue)){

    accountIds.add(opp.accountId);

    }

    else if(Trigger.isUpdate && (opp.AccountId != Trigger.oldMap.get(opp.Id).AccountId)){

    accountIds.add(opp.accountId);

    accountIds.add(Trigger.oldMap.get(opp.Id).accountId);

    }

    }

    for(AggregateResult ar : [SELECT AccountId AcctId,SUM(ExpectedRevenue) totalRevenue FROM Opportunity WHERE AccountId in :accountIds group by AccountId]){

    accountListToUpdate.add(

    new Account(

    Id = (Id) ar.get('AcctId'),

    TotalRevenue__c = (Integer) ar.get('totalRevenue');

    )

    );

    }

    if(!accountListToUpdate.isEmpty())

    UPDATE accountListToUpdate;

    }

    In case you find any other issue please mention. 

    If you find your Solution then mark this as the best answer. 

0/9000

Design a trigger to automatically assign new Case records to specific users based on the Case's Type field. For example, Cases of type 'Technical Support' should be assigned to User A, and Cases of type 'Billing' should be assigned to User B.

 

#Triggers

2 answers
  1. Dec 15, 2024, 5:22 PM

    Hii,

    It's better if you share the approach you followed.

    However, you can use something like this in your trigger handler

     

     //You can fetch the user Ids through query and use them instead of hardcoding

     Id userAId = 'xxxxxxxxxxxx'; // User A's Salesforce ID

     Id userBId = 'xxxxxxxxxxxx'; // User B's Salesforce ID

     

     // Loop through all the Case records being inserted

     for (Case newCase : Trigger.new) {

     

     // Check the 'Type' field to determine which user should be assigned

     if (newCase.Type == 'Technical Support') {

     newCase.OwnerId = userAId; // Assign to User A

     } else if (newCase.Type == 'Billing') {

     newCase.OwnerId = userBId; // Assign to User B

     } else {

     // newCase.OwnerId = defaultUserId;

     }

     }

0/9000

trigger CaseDeleted on Case (before delete) 

{

   //Whenever a case is getting deleted check if it has the contact is related account

   // If it is related to the account and contact then throw an error saying "can not be deleted"

   for (Case cs: Trigger. old);

   {

       if(case.Accountid !=Null && case.Contactid !=Null) // if case is releted Account and Contact

       

       {

       case.adderror('we can not delete case Releted Account And Contact');

       }

   }

}

Explain The Execution

2 answers
  1. Nov 18, 2024, 2:58 PM

    Hi,

    The trigger prevents the deletion of a Case record if it is associated with both an Account and a Contact. Before the record is deleted, the trigger checks whether the Case has non-null values for both the AccountId and ContactId. If both are present, the trigger calls addError() on the Case record, which stops the deletion and displays an error message to the user, indicating that the Case cannot be deleted because it is linked to both an Account and a Contact.

    The issue in your code is semicolon after for loop,the code should be :

    trigger CaseDeleted on Case (before delete) {

     

    // Loop through each case in the trigger's old context (cases that are being deleted)

    for (Case cs : Trigger.old) {

     

    // Check if both AccountId and ContactId are populated

    if (cs.AccountId != null && cs.ContactId != null) {

    // If the case is related to both an Account and a Contact, throw an error to prevent deletion

    cs.addError('Cannot delete case related to both an Account and a Contact.');

    }

     

    }

     

    }

    Mark this helpful if it solves your query!

0/9000

Write a trigger that prevents the insertion of an Account if the Account's Annual Revenue is less than $50,000. The trigger should also prevent updates that would set the Annual Revenue to less than $50,000.

 

#Triggers

5 answers
  1. Nov 7, 2024, 6:17 AM

    Hi Swati,

    Try this:

    trigger PreventLowAnnualRevenue on Account (before insert, before update) {

    // Iterate through each Account record in the trigger context

    for (Account acc : Trigger.new) {

     

    // Check for insert operation

    if (Trigger.isInsert) {

    // Prevent insert if AnnualRevenue is less than $50,000

    if (acc.AnnualRevenue != null && acc.AnnualRevenue < 50000) {

    acc.addError('Annual Revenue cannot be less than $50,000.');

    }

    }

     

    // Check for update operation

    if (Trigger.isUpdate) {

    // Prevent update if AnnualRevenue is being set below $50,000

    Account oldAcc = Trigger.oldMap.get(acc.Id);

    if (acc.AnnualRevenue != oldAcc.AnnualRevenue && acc.AnnualRevenue < 50000) {

    acc.addError('Annual Revenue cannot be set to less than $50,000.');

    }

    }

    }

    }

    Mark this helpful if it solves your query!

0/9000

Want create CSV file from APEX

 

Hi All,

 

Hope you doing well. I am trying create CSV file from apex class. Where i am getting the object name from custom label and depend on the label i query all the fields of that object and then store the object API name and all the fields in  Single Object Data Storage custom object and after that depends on the fields trying create a CSV file and try to store that csv file in Single Object Data Storage custom object attachment.

 

Example: in Custom Label having "Account" api name, so when i run the class, it query all the fields of account and create a record in Single Object Data Storage custom object with "Account" name and the account fields. And after using that query fields i am creating the CSV and store in the same  Single Object Data Storage custom object record. 

 

As of now, everything working fine, i can able to create the CSV file and store it in Single Object Data Storage custom object, but the CSV file data are not coming proper way. I understand where is the issue but can't find a way to fix it. If anyone can help?

Here is excel screenshot i am getting:

 

Want create CSV file from APEX Hi All, Hope you doing well. I am trying create CSV file from apex class.

 

Below is my code:

public class SingleObjectDataStoreCls {

    

    public static void SingleObjectDataStoreMtd() {

        

        ID singleObjDataStorageID;

        List<String> NameFiled = new List<String>();

        Map <String,Schema.SObjectType> gd = Schema.getGlobalDescribe();

        

        //Custom label for SObjcet api name: System.label.SObject_Name

        Schema.SObjectType sobjType = gd.get(System.label.SObject_Name);

        Schema.DescribeSObjectResult r = sobjType.getDescribe();

        Map<String, Schema.SObjectField> MapofField = r.fields.getMap();

        

        for(String fieldName : MapofField.keySet()) 

        {

        Schema.SObjectField field = MapofField.get(fieldName);

        Schema.DescribeFieldResult F = field.getDescribe();

        NameFiled.add(F.getName());  

        }        

        System.debug('Field Name:***2 '+NameFiled);

 

        String allstring = String.join(new List<String>(NameFiled), ', ');

        System.debug(allstring);

        

//Single Object Data Storage custom object

        Single_Object_Data_Storage__c singleObjDataStorage = new Single_Object_Data_Storage__c();

        singleObjDataStorage.Fields_api_name_of_selected_object__c= allstring; //adding object fileds from query

        singleObjDataStorage.Object_API_name_of_selected_object__c = System.label.SObject_Name; //adding object name from query

        insert singleObjDataStorage;

        System.debug('Sujay1 singleObjDataStorage '+singleObjDataStorage);

        

        singleObjDataStorageID = singleObjDataStorage.Id;

        System.debug('Sujay1 singleObjDataStorageID '+singleObjDataStorageID);

                

        //get fields values

        SObject[] singleObjDataStorages = Database.query('SELECT ' + singleObjDataStorage.Fields_api_name_of_selected_object__c + ' FROM ' +System.label.SObject_Name );

 

        //create CSV

        String title = System.label.SObject_Name;

        String csvFile;

        String csvColumnHeader = singleObjDataStorage.Fields_api_name_of_selected_object__c+', Type\n';

        List<String> csvRowValues = new List<String>();

 csvFile = csvColumnHeader + String.join(singleObjDataStorages,'\n');

        

        title += '.csv';

        

        createFile(csvFile, title, singleObjDataStorageID);

    }

    

    public static void createFile(String csvFile, String title, Id singleObjDataStorageID) {

        

        ContentVersion cv = new ContentVersion();

        cv.ContentLocation = 'S'; 

        cv.Title = title; 

        cv.PathOnClient = title; 

        cv.VersionData = Blob.valueOf(csvFile); //file data

        insert cv;

        

        Attachment attachment = new Attachment();

        attachment.ParentId = 'a04J2000009p0aBIAQ';

        attachment.Name = cv.PathOnClient;

        attachment.Body = cv.VersionData;

        insert attachment;

    }

 

}

 

#Apex  #Salesforce Developer  #Triggers  #LWC

 

#Apex  #Salesforce Developer  #Salesforce  #Triggers  #LWC

1 comment
  1. Aug 30, 2024, 10:17 AM

    OK While you are creating CSV you must be careful about these things in your data:

    - Comma inside data

    - New line and Line feed values.

    As well as I see you are only assigning   Database.query results as CSV, you should do in loop and control the values inside fields.

     

     SObject[] singleObjDataStorages = Database.query('SELECT ' + singleObjDataStorage.Fields_api_name_of_selected_object__c + ' FROM ' +System.label.SObject_Name );

0/9000

🔍 Seeking Advice from Salesforce Wizards 🔍

 

Hey, Salesforce experts!

I'm currently tackling a tricky logic puzzle in Salesforce, and I could use some guidance.

 

Here's the scenario: I have a requirement to ensure that two lookup fields on a record maintain a one-to-one mapping. To accomplish this, I've implemented a before insert/update trigger to enforce the mapping. Additionally, I've set up a validation rule to prevent changes to the lookup fields once they are populated.

 

Now, according to Salesforce's order of execution, validation rules should run before triggers. However, I'm encountering a situation where the trigger error is firing first, even though I expect the validation rule to take precedence, when i am trying a update a lookup field after it's already populated.

 

Feel free to drop your thoughts in the comments below or reach out to me directly. Your expertise could be the missing piece of the puzzle!

 

#Salesforce Developer  #Automation  #Apex  #Triggers  #Validation Rule  #Validation Rules

1 comment
  1. May 14, 2024, 10:04 PM

    Hi @Adarsh Agrawal,

     

    Please refer to the documentation below.

     

    Triggers and Order of Execution | Apex Developer Guide | Salesforce Developers

     

    1. Salesforce performs different validation checks depending on the type of request.
      • For requests from a standard UI edit page, Salesforce runs these system validation checks on the record:
        • Compliance with layout-specific rules
        • Required values at the layout level and field-definition level
        • Valid field formats
        • Maximum field length
      • Additionally, if the request is from a user object on a standard UI edit page, Salesforce runs custom validation rules.
      • For requests from multiline item creation such as quote line items and opportunity line items, Salesforce runs custom validation rules.
      • For requests from other sources such as an Apex application or a SOAP API call, Salesforce validates only the foreign keys and restricted picklists. Before executing a trigger, Salesforce verifies that any custom foreign keys don’t refer to the object itself.
    2. Executes record-triggered flows that are configured to run before the record is saved.
    3. Executes all before triggers.
    4. Runs most system validation steps again, such as verifying that all required fields have a non-null value and runs any custom validation rules.

    Please check the fourth point.

     

    Thanks!

0/9000

Hi All, 

 

I've been struggling with achieving the 75%+ coverage required for a trigger to prevent users from deleting files linked to Account. 

 

The trigger works as intended; allowing System Administrators OR users with a "File_Deletion" permission set to delete files 

 

Any advice is much appreciated!

 

Trigger

trigger PreventFileDeletion on ContentDocument (before delete) {    Boolean isAdmin = false;    Boolean hasPermissionSet = false;    // Check if running user has the SysAdmin profile    Profile adminProfile = [SELECT Id, Name FROM Profile WHERE Name = 'System Administrator' LIMIT 1];    if (UserInfo.getProfileId() == adminProfile.Id) {        isAdmin = true;    }    // Check if running user has "File Deletion" permission set if user is not SysAdmin    if(isAdmin == false){        PermissionSet ps = [SELECT Id, Name FROM PermissionSet WHERE Name = 'File_Deletion' LIMIT 1];        // Get current user's Id        Id userId = UserInfo.getUserId();         // Check for PermSet allowing deletion        PermissionSetAssignment psa = [SELECT id FROM PermissionSetAssignment WHERE AssigneeId = :userId AND PermissionSetId = :ps.id LIMIT 1];        if (psa != null) {            hasPermissionSet = true;        }    }    // If running user not SysAdmin and does not have "File Deletion" permission set run the logic    if (!isAdmin && !hasPermissionSet) {        // Check if any related ContentDocumentLink records exist with related AccountIds        Set<Id> accountIds = new Set<Id>();        // Gather ContentDocumentIds being deleted        for (ContentDocument cd : Trigger.old) {            accountIds.add(cd.Id);        }        // Query related ContentDocumentLink records        List<ContentDocumentLink> relatedLinks = [SELECT Id, LinkedEntityId FROM ContentDocumentLink WHERE ContentDocumentId IN :accountIds];        // Check if any related ContentDocumentLink records exist with AccountIds        for (ContentDocumentLink link : relatedLinks) {            if (link.LinkedEntityId.getSObjectType() == Account.SObjectType) {                // Prevent deletion and throw an error message                Trigger.oldMap.get(link.ContentDocumentId).addError('Files related to Accounts cannot be deleted.');            }        }    }}

Test Class (36% coverage)

@isTestprivate class TestPreventFileDeletion {    private static PermissionSet ps;    private static User SystemAdminUser;    private static User StandardUser;    private static User PermSetUser;    @TestSetup    static void setup() {        // Create test Acc        Account testAccount = new Account(name = 'Acme');        insert testAccount;        // Create test ContentDocument        ContentVersion cv = new ContentVersion();        cv.Title = 'Test Document';        cv.PathOnClient = 'test_document.txt';        cv.VersionData = Blob.valueOf('Test Content');        insert cv;        // Create a ContentDocumentLink between Acc & ContentDocument        ContentDocumentLink testLink = new ContentDocumentLink();        testLink.ContentDocumentId = [SELECT ContentDocumentId FROM ContentVersion WHERE Id = :cv.Id].ContentDocumentId;        testLink.LinkedEntityId = testAccount.Id;        testLink.ShareType = 'V';        insert testLink;    }    @isTest    static void AllowSystemAdminUser() {        // Retrieve profile for test        Profile adminProfile = [SELECT Id FROM Profile WHERE Name = 'System Administrator' LIMIT 1];        // Create SysAdmin user        SystemAdminUser = new User(            Alias = 'SysAdUsr',            Email = 'SystemAdminUser@test.com',            EmailEncodingKey = 'UTF-8',            LastName = 'Testing',            LanguageLocaleKey = 'en_US',            LocaleSidKey = 'en_US',            ProfileId = adminProfile.Id,            TimeZoneSidKey = 'America/Los_Angeles',            UserName = 'SystemAdminUser@test.com.hdyfjtutkg'        );        insert SystemAdminUser;        // Get Acc        Account testAccount = [SELECT id FROM Account LIMIT 1];        // Query ContentDocumentLink records related to the test Account        List<Id> relatedContentDocumentIds = new List<Id>();        for (ContentDocumentLink link : [SELECT ContentDocumentId FROM ContentDocumentLink WHERE LinkedEntityId = :testAccount.Id]) {            relatedContentDocumentIds.add(link.ContentDocumentId);        }        Test.startTest();        System.RunAs(SystemAdminUser){        // Attempt to delete the test ContentDocument as SystemAdmin            try {                delete [SELECT Id FROM ContentDocument WHERE Id IN :relatedContentDocumentIds];                //System.assert(false, 'Expected error not thrown');            } catch (DmlException e) {                //System.assert(e.getDmlMessage(0).contains('Files related to Accounts cannot be deleted.'), 'Unexpected error message');                System.debug('e.getDmlMessage(0) = ' + e.getDmlMessage(0));            }        }        Test.stopTest();    }    @isTest    static void PreventStandardUser() {        // Get Id the Standard User Profile        Profile StandardProfile = [SELECT Id FROM Profile WHERE Name = 'Standard User' LIMIT 1];        // Create Standard Profile user        StandardUser = new User(            Alias = 'StandUsr',            Email = 'StandardUser@test.com',            EmailEncodingKey = 'UTF-8',            LastName = 'Testing',            LanguageLocaleKey = 'en_US',            LocaleSidKey = 'en_US',            ProfileId = StandardProfile.Id,            TimeZoneSidKey = 'America/Los_Angeles',            UserName = 'StandardUser@test.com.hdyfjtutkg'        );        insert StandardUser;        // Get Acc         Account testAccount = [SELECT id FROM Account LIMIT 1];        // Query ContentDocumentLink records related to the test Account        List<Id> relatedContentDocumentIds = new List<Id>();        for (ContentDocumentLink link : [SELECT ContentDocumentId FROM ContentDocumentLink WHERE LinkedEntityId = :testAccount.Id]) {            relatedContentDocumentIds.add(link.ContentDocumentId);        }        Test.startTest();        // Attempt to delete the test ContentDocument as Standard User        System.runAs(StandardUser){            try {                delete [SELECT Id FROM ContentDocument WHERE Id IN :relatedContentDocumentIds];                //System.assert(false, 'Expected error not thrown');            } catch (DmlException e) {                System.assert(e.getDmlMessage(0).contains('insufficient access rights on object id'), 'Unexpected error message');            }            }        Test.stopTest();    }    @isTest    static void AllowPermSetUser() {        // Get Id for Standard User Profile        Profile StandardUser = [SELECT Id FROM Profile WHERE Name = 'Standard User' LIMIT 1];        // Retrieve Account for testing        Account testAccount = [SELECT id FROM Account LIMIT 1];        // Create a Standard Profile test user with PermSet        PermSetUser = new User(            Alias = 'PermSUsr',            Email = 'PermSetUser@test.com',            EmailEncodingKey = 'UTF-8',            LastName = 'Testing',            LanguageLocaleKey = 'en_US',            LocaleSidKey = 'en_US',            ProfileId = StandardUser.Id,            TimeZoneSidKey = 'America/Los_Angeles',            UserName = 'PermSetUser@test.com.hdyfjtutkg'        );        insert PermSetUser;        // Get the 'File_Deletion' permission set        ps = [SELECT id FROM PermissionSet WHERE Name='File_Deletion'];        // Assign Permission Set to the Standard User        PermissionSetAssignment psa = new PermissionSetAssignment(AssigneeId=PermSetUser.Id, PermissionSetId=ps.Id);        insert psa;        // Query ContentDocumentLink records related to the test Account        List<Id> relatedContentDocumentIds = new List<Id>();        for (ContentDocumentLink link : [SELECT ContentDocumentId FROM ContentDocumentLink WHERE LinkedEntityId = :testAccount.Id]) {            relatedContentDocumentIds.add(link.ContentDocumentId);        }        Test.startTest();        System.RunAs(PermSetUser){        // Attempt to delete the test ContentDocument as Standard User            try {                delete [SELECT Id FROM ContentDocument WHERE Id IN :relatedContentDocumentIds];                //System.assert(false, 'Expected error not thrown');            } catch (DmlException e) {                //System.assert(e.getDmlMessage(0).contains('Files related to Accounts cannot be deleted.'), 'Unexpected error message');            }        }        Test.stopTest();    }}

 

Trigger to Prevent File Deletion - Test Class Coverage Question

#Apex #Test Classes #Salesforce Developer #Salesforce Developers #Apex Class #Triggers #Developer Forums

2 answers
  1. Mar 20, 2024, 12:57 PM

    Sometimes in your if statements - like on line #11 - I'll do an "|| test.isRunningTest()" - and that way the loop will run during test - to get more test coverage.

    It's probably not the "correct" way to do it, but I works.

0/9000

Develop a trigger that automatically associates new Contact records with an Account based on a custom field on the Contact record called ExternalAccountID matching the AccountNumber of an Account.

 

#Triggers

3 answers
  1. Mar 13, 2024, 12:15 PM

    Hi @Swati Patle

     

    For this requirement you can write trigger like this.

    trigger ContactWithAccount on Contact (before insert, before update) {

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

    Set<Id> accIds = new Set<Id>();

    for (Contact con: Trigger.new) {

    accIds.add(con.AccountId);

    }

    Map<Id, Account> accMap = new Map<Id, Account>([SELECT Id, AccountNumber FROM Account WHERE Id IN :accIds]);

    for (Contact con: Trigger.new) {

    Account acc = accMap?.get(con.AccountId);

    if (con.ExternalAccountID__c == acc.AccountNumber) {

    con.AccountId = acc.Id;

    } else {

    con.adderror('To associate Contact with Account ExternalAccountID and Account\'s AccountNumber should be same');

    }

    }

    }

    }

    Hope this will solve your problem.

0/9000