Your long-standing Salesforce Consultants
5 results found with an empty search
- Salesforce Trigger Framework (Code Version): A Recursion-Safe, Bulk-Safe, Scalable Architecture (Solve SOQL 101 error and DML 151 Error)
In our previous post, we shared the story of how we encountered trigger recursion, SOQL limit breaches, and performance issues when multiple automation layers (Flows, PB, Workflows, Integrations) acted on the same records. As promised, here is the complete code version of the recursion-safe, enterprise-ready Salesforce Trigger Framework we implemented to solve this problem. This design keeps the trigger clean, avoids repeated execution on the same records, separates logic by context (insert/update/delete), and ensures governor-limit-safe execution in every scenario. Core Principles of the Framework Before jumping into the code, here are the design principles: Single Trigger Per Object No mixing logic across multiple triggers. Switch-based Context Handling Clear separation for before insert, before update, after update, etc. Recursion Control Prevent the same record from being processed twice inside the same transaction. Bulk-Safe Design All logic runs on lists/maps—no per-record SOQL or DML. Cleaner Maintenance Each event has its own handler method. The Trigger (Generic, Clean, Structured) trigger ObjName_trigger on ObjName (before delete, after delete, before insert, after insert, before update, after update) { Switch on Trigger.operationType { when BEFORE_INSERT { if (trigger_cntrl.is_before_insert) { trigger_cntrl.beforeInsert(Trigger.New,Trigger.Old, Trigger.Newmap,Trigger.Oldmap); } } when BEFORE_UPDATE { list list_of_new_obj = new list(); map new_map_of_obj = new map(); for(Obj current_Obj : trigger.new) { if(!trigger_cntrl.set_b_update.contains(current_Obj.id)){ list_of_new_obj.add(current_Obj); new_map_of_obj .put(current_Obj.id,current_Obj); trigger_cntrl.set_b_update.add(current_Obj.id); } } if(!list_of_new_obj.isempty()){ trigger_cntrl.beforeupdate(list_of_new_obj,Trigger.Old, new_map_of_obj,Trigger.Oldmap); } } when AFTER_INSERT { list list_of_new_Obj = new list(); map new_map_of_Obj = new map(); for(Obj current_Obj : trigger.new) { if(!trigger_cntrl.set_a_insert.contains(current_Obj.id)){ list_of_new_Obj.add(current_Obj); new_map_of_Obj.put(current_Obj.id,current_Obj); trigger_cntrl.set_a_insert.add(current_Obj.id); } } if(!list_of_new_Obj.isempty()) { trigger_cntrl.afterInsert(list_of_new_Obj,Trigger.Old, new_map_of_Obj,Trigger.Oldmap); } } when AFTER_UPDATE { list list_of_new_Obj = new list(); map new_map_of_Obj = new map(); for(Objcurrent_Obj : trigger.new){ if(!trigger_cntrl.set_a_update.contains(current_Obj.id)){ list_of_new_obj.add(current_obj); new_map_of_obj.put(current_obj.id,current_obj); trigger_cntrl.set_a_update.add(current_obj.id); } } if(!list_of_new_obj.isempty()){ trigger_cntrl.afterUpdate(list_of_new_obj,Trigger.Old, new_map_of_obj,Trigger.Oldmap); } } when BEFORE_DELETE { trigger_cntrl.beforedelete(Trigger.New,Trigger.Old, Trigger.Newmap,Trigger.Oldmap); } } } The Controller Class (Recursion-Safe Handler) Below is the clean controller class that receives filtered lists/maps and contains dedicated methods for each trigger context. public class Account_trigger_cntrl { public static Boolean is_before_insert = true; public static Boolean is_after_insert = true; public static Set set_a_insert = new Set(); public static Set set_a_update = new Set(); public static Set set_b_update = new Set(); public static void beforeInsert(list new_list_obj, list old_list_obj, map new_map_obj, map old_map_obj) { system.debug('inside before insert trigger'); } public static void beforeupdate(list new_list_obj, list old_list_obj, map new_map_obj, map old_map_obj) { system.debug('inside before update trigger'); } public static void beforedelete(list new_list_obj, list old_list_obj, map new_map_obj, map old_map_obj) { system.debug('inside before delete trigger'); } public static void afterInsert(list new_list_obj, list old_list_obj, map new_map_obj, map old_map_obj) { system.debug('inside after Insert trigger'); } public static void afterUpdate(list new_list_obj, list old_list_obj, map new_map_obj, map old_map_obj) { system.debug('inside after update trigger'); } Why This Framework Works Prevents Multiple Executions Records are processed only once using the recursion set. Supports Heavy Automation Flows + PB + Apex + Integrations all work without causing trigger loops. Bulk Friendly All logic is list-based and executes safely during bulk record operations. Maintainable & Expandable Each method corresponds to a specific event, making enhancements easy. Stable in Production No more SOQL 101 error No more DML 151 error in trigger. No more unpredictable recursion. 🎯 Final Thoughts This trigger framework has become our default structure for all Salesforce implementations. It ensures: Predictable behavior No unexpected recursion Clean separation of logic Full alignment with governor limits Easy debugging and future enhancements If your org is scaling or using multiple automation layers, adopting a framework like this is not optional—it’s essential.
- How we built a scalable, enterprise-grade Salesforce trigger framework that stopped failures—no more SOQL Errors, Recursion Loops, or Performance Bottlenecks (No Code Version)
That moment happens to every Salesforce developer: something that worked perfectly for weeks suddenly stops working in production. It was a little trigger that didn't look dangerous to me. It worked perfectly... Until the day it didn't. This is the story of how a simple trigger led to SOQL limit errors, recursion loops, and bulk-update failures. I then rebuilt everything into a clean, scalable trigger framework that has never failed since. The Easy Trigger That Became a Mess It all began with one clean trigger and a class for handling it. But as the organization got bigger, so did the layers of automation: Flow Process Builder Rules for Validation Changes to the workflow Integrations All of them are changing the same records, which keeps setting off my logic. This led to: 101 SOQL limit errors 151 DML limit errors Recursive updates Bulk operation failures A small trigger turned into a nightmare for production. Attempt #2: Queueable Apex (A Quick Fix) To ease the pressure on the trigger, I moved logic to a Queueable. It helped, but then it caused more problems: Jobs began to pile up Several jobs changed the same records Updates in real time were delayed It was hard to predict what order the updates would come in. Queueables weren't the answer for the long term. I needed something that was safe for bulk use, worked at the same time, and was always the same. The Breakthrough: A Trigger Framework That Is Safe for Recursion It was clear what the problem was: the same records were being updated over and over in one transaction. So I rebuilt everything using a structured Trigger Framework that was made to: Keep logic separate Handle Insert/Update/Delete contexts correctly Keep things safe when doing a lot of them at once Protect against recursion Make sure that Flows, PB, and Integrations all behave the same way How the Final Trigger Framework Looks (in Theory) One Trigger for Each Object No more triggers that happen more than once. One trigger sends all logic to a central controller. Handling Based on Context Every event has its own way to clean: Before Insert Before Update After Inserting After Update Before Deleting After Deleting Simple to read. Simple to fix. Layer of Protection for Recursion A simple but strong idea: Skip a record if it has already been processed in a transaction. This got rid of: Loops that go on forever Updates that you don't want to see again Chains that trigger flow Governor limits failures Only process the records that need it The framework only processes the records that really need it, even if 200 records enter the trigger. This lowers the load and keeps the limits from being reached. Centralized Controller Class All of the logic is in a special class: Logic to be inserted before in one method Change the logic in another Separate delete logic No mixing. No mix-up. Completely modular. Safe by Default for Bulk The framework makes sure that: There are no SOQL/DML loops, All operations run on lists, and It can handle large amounts of data. And governor limits stay happy. The Result: No Errors Since It Was Put in Place After switching to this framework: No more SOQL 101 mistakes. No problems with DML limits Recursion loops are gone. Bulk updates went off without a hitch. Debugging got easier The flows, PB, and triggers no longer stepped on each other. Most importantly, the system became predictable. Final Thoughts This experience taught me a very important lesson: Triggers don't fail because they make sense. They don't work because of the way they're set up. A simple trigger might work for now, but as the organization grows, it needs a base that can grow with it. This Trigger Framework, which is safe from recursion and based on events, is now the standard for all of my Salesforce projects. Clean, easy to scale, and easy to keep up. And most importantly — limit-proof. CODE VERSION IS ALSO COMING UP
- Wrapper Class in LWC (Lightning Web Components)
In Salesforce development, an Apex class is used to create objects that hold values of fixed data types. For example, an Integer variable stores only integers, and a String variable stores only text. However, when a developer needs to return multiple values of different types — such as a mix of Contact fields, Account fields, booleans, or even custom strings — a standard SObject is not enough. This is where a Wrapper Class becomes extremely useful. What Is a Wrapper Class? A Wrapper Class is a custom Apex class that works as a container. It allows you to group different types of data together inside one object. Key features: Can store multiple data types (String, Boolean, Objects, IDs, etc.) Helps return multiple values from Apex to LWC Useful for datatables, checkboxes, and combining related/unrelated objects Creating a Wrapper Class Inside Apex Wrapper classes are usually created inside an Apex controller. public class Account_Controller { public class wrap_account_details { public Account acc {get; set;} public Boolean selected {get; set;} } } Initializing Wrapper Class Variables Account acc_obj = [SELECT Id, Name FROM Account LIMIT 1]; wrap_account_details wrapperObj = new wrap_account_details(); wrapperObj.acc = acc_obj; wrapperObj.selected = true; Returning Wrapper Class Data Return Single Wrapper Object public static wrap_account_details Account_details(){ wrap_account_details wrapperObj = new wrap_account_details(); wrapperObj.acc = [SELECT Id, Name FROM Account LIMIT 1]; wrapperObj.selected = true; return wrapperObj; } Return Multiple Records (List of Wrapper Objects) List wrapperList = new List(); Populate inside a loop and return the list. Real Project Example Goal: Return a combination of Contact fields Associated Account Name A custom string variable Display all data in a Lightning Datatable in LWC This cannot be done using standard SObjects. So we build a wrapper class. 🧩 Apex Controller: Account_contact_wrapper_controller.cls public class Account_contact_wrapper_controller { @AuraEnabled(cacheable=true) public static List getAllAccountWithContacts(){ List accWrapperList = new List(); List cont_list = [ SELECT Id, Name, Phone, Email, Account.Name FROM Contact WHERE Account.Name != null LIMIT 5 ]; if(!cont_list.isEmpty()){ for(Contact c : cont_list){ Account_Contact_Wrapper w = new Account_Contact_Wrapper(); w.accName = c.Account.Name; w.contactName = c.Name; w.contactEmail = c.Email; w.phoneNumber = c.Phone; w.textMessage = 'Have a Nice Day..!!'; accWrapperList.add(w); } } return accWrapperList; } public class Account_Contact_Wrapper{ @AuraEnabled public String accName; @AuraEnabled public String contactName; @AuraEnabled public String contactEmail; @AuraEnabled public String textMessage; @AuraEnabled public String phoneNumber; } 🧩 LWC HTML: Wrapperlwc_demo.html {error} 🧩 LWC JS: Wrapperlwc_demo.js import { LightningElement, wire } from 'lwc'; import getAllAccountWithContacts from '@salesforce/apex/Account_contact_wrapper_controller.getAllAccountWithContacts'; export default class WrapperClassExampleLWC extends LightningElement { columns = [{ label: 'Account Name', fieldName: 'accName' }, { label: 'Contact Name', fieldName: 'contactName' }, { label: 'Email', fieldName: 'contactEmail' } ]; accountsWithContacts; error; @wire(getAllAccountWithContacts) wiredAccountsWithContacts({ error, data }) { if (data) { this.accountsWithContacts = data; } else if (error) { this.error = error; } } } 🧩 Meta XML File: Wrapperlwc_demo.js-meta.xml 48.0 true lightning__AppPage lightning__RecordPagelightning__HomePage Summary In this article, we: ✔ Created a Wrapper Class inside an Apex controller ✔ Wrapped Contact fields + Account Name + Custom variables ✔ Added data into a wrapper list ✔ Returned the list to LWC ✔ Displayed everything using a Lightning Datatable Wrapper classes are one of the most powerful concepts when building scalable LWC and Apex applications.
- Search the data loaded into the Datatable in Lightning Web Component (LWC).
Look in the datatable for previously loaded information without accessing the server's database each time. This results in an instantaneous response and fewer visits to the server. Users may use the feature to search for a specific item in a list. Users can rapidly retrieve records by entering initials rather than reading through a long list. How the Search filter works Let's use the example of a table showing a list of more than 500 records. The search box compares text input by the user against all 500 records and provides the record that matches the text or it returns blank. Sample Project Let's build a search filter that looks through the contact record and shows the results on a lightning datatable. search_filter_lwc.HTML search_filter_lwc.JS import { LightningElement , wire } from 'lwc'; import get_contact_details from '@salesforce/apex/search_filter_lwc_controller.get_contact_details'; export default class Search_filter_lwc extends LightningElement { searchKey columns = [ { label: 'Name', fieldName: 'Name' }, { label: 'Phone', fieldName: 'Phone' }, { label: 'Email ', fieldName: 'Email' }, ]; data error contacts_data = []; retrieved_data = [] @wire(get_contact_details) wired_details({ error, data }) { if (data) { console.log('data',data); this.contacts_data = data; this.retrieved_data = data; } else if (error) { console.log(error); this.error = error; } } notes_search(event){ var name_search = event.target.value; console.log('notes_search--',name_search); var searchString = name_search.toUpperCase(); console.log('searchString--',searchString); var allRecords = this.retrieved_data; var new_search_result = [] console.log('allRecords--',allRecords); var i; for (i = 0; i < allRecords.length; i++) { if ((allRecords[i].Name) && searchString != '' &&(allRecords[i].Name.toUpperCase().includes(searchString))) { new_search_result.push(allRecords[i]); console.log('match--'); } } console.log('new_search_result--',new_search_result); if(new_search_result.length !=0){ this.contacts_data = new_search_result console.log('not blank--'); }else if((new_search_result.length ==0 && searchString != '')){ this.contacts_data = []; }else{ this.contacts_data = this.retrieved_data console.log('blank--'); } } } Apex Controller Class(search_filter_lwc_controller) public class search_filter_lwc_controller { @AuraEnabled(cacheable=true) public static list get_contact_details(){ list cont_list = [select id,name,phone,Email from contact limit 10]; return cont_list; } } OUTPUT entered "sa" in the search bar.
- CRM Consulting – Key Business Benefits
CRM (Customer Relationship Management) is an essential tool that enables businesses to manage and optimize their customer interactions. Implementing a CRM system can be a complex process that requires careful planning, evaluation, and execution. While CRM consulting and CRM implementation are related, they are distinct services that play different roles in the overall CRM process. In this article, we'll explore the difference between CRM consulting and CRM implementation. CRM Consulting: What it is and Why it's Important? CRM consulting is a strategic service that provides businesses with advice and guidance on how to optimize their customer relationship management processes. A CRM consultant works with the business to understand its goals, challenges, and processes and provides insights and recommendations on how to optimize them. The role of a CRM consultant is to help businesses make informed decisions about their CRM strategy. The key benefits of CRM consulting include: Identifying Business Needs: A CRM consultant can help businesses identify their needs and goals for a CRM system. They can analyze existing processes and recommend best practices to improve customer engagement and satisfaction. Selecting the Right CRM Solution: With the extensive knowledge and experience in CRM software, a CRM consultant can help businesses select the right CRM solution that meets their unique needs. Creating a Strategic Plan: A CRM consultant can help businesses create a strategic plan for implementing CRM software. This includes assessing the business's readiness for implementation, identifying key stakeholders, defining project goals, and creating a roadmap for successful implementation. Optimizing CRM Processes: By optimizing their CRM processes, businesses can improve their return on investment (ROI) in CRM software. A CRM consultant can help businesses identify areas for improvement and recommend best practices to increase customer engagement and satisfaction





