Why Apex Trigger Best Practices Matter for Your Sanity
If you’ve ever had to debug a recursive loop at 4 PM on a Friday, you know why Apex Trigger Best Practices are so important for keeping your sanity. Triggers are incredibly powerful, but they’re also the easiest way to break an entire Salesforce org if you aren’t careful. I’ve spent years cleaning up “trigger soup” where logic was scattered everywhere, and trust me, it’s not a fun way to spend your week.
In my experience, the difference between a senior developer and a junior one isn’t just knowing how to write code. It’s knowing how to structure that code so it doesn’t fall apart when the business decides to upload 50,000 records via the API. Let’s look at how we can build triggers that actually scale.
One Trigger to Rule Them All
I’m a big believer in the “one trigger per object” rule. When you have five different triggers on the Account object, you’re basically playing Russian roulette with the order of execution. Salesforce doesn’t guarantee which one runs first, and that’s a recipe for bugs that are impossible to track down. If you’re still figuring out what an Apex trigger actually is and how it fits into the platform, just remember: keep it to one file per object.
Instead of putting all your logic inside the trigger itself, you should delegate everything to a handler class. This keeps your trigger file clean and makes your logic much easier to test. Here’s a simple pattern I use in almost every project:
trigger AccountTrigger on Account (before insert, before update, after insert, after update) {
AccountTriggerHandler handler = new AccountTriggerHandler(Trigger.new, Trigger.oldMap);
if (Trigger.isBefore) {
if (Trigger.isInsert) handler.beforeInsert();
if (Trigger.isUpdate) handler.beforeUpdate();
} else if (Trigger.isAfter) {
if (Trigger.isInsert) handler.afterInsert();
if (Trigger.isUpdate) handler.afterUpdate();
}
}
Master Bulkification as Part of Your Apex Trigger Best Practices
Here’s the thing about governor limits: they don’t care if your code works for a single record in the UI. If it fails when a Data Loader job hits it with 200 records, it’s broken. You’ve got to stop putting SOQL queries inside “for” loops. It’s the most basic mistake, but I still see it in enterprise orgs all the time. Use Maps and Sets to handle data in bulk.
When you’re working with related data, query it once, put it in a Map, and then use that Map inside your loop. This keeps your query count low and your performance high. If you’re worried about hitting asynchronous Apex limits or synchronous SOQL limits, bulkification is your best friend. It’s not just a “nice to have” – it’s a requirement for any production-ready code.
Always assume Trigger.new contains 200 records. If you write your logic with that mindset, you’ll almost never hit a limit exception in production.
Handling Recursion and Complex Logic
Recursion is what happens when an “after update” trigger updates the same record again, causing the trigger to fire a second time. If you don’t have a guard in place, you’ll hit a limit or end up with messy data. I usually use a simple static Boolean variable in my handler or a dedicated “recursion guard” class to check if the logic has already run for the current transaction.
But don’t just stop at recursion. You also need to think about where your logic lives. If you have a long-running process, like a callout to an external ERP, move that to a Queueable or @future method. Keeping the trigger execution short means the user isn’t stuck staring at a loading spinner for ten seconds every time they click save.
Applying Apex Trigger Best Practices to Real-World Scenarios
One thing that trips people up is hardcoding IDs. Please, don’t do it. Use Custom Metadata Types or Custom Settings to store configuration values like Record Type IDs or integration endpoints. This lets you change behavior in production without needing a full code deployment. It’s a small change that makes your life much easier during a release.
Writing Tests That Actually Matter
We’ve all seen tests that just exist to hit the 75% coverage requirement. Don’t be that developer. Write tests that actually verify your business logic works. Test for single records, test for a batch of 200, and test for “negative” scenarios where the data is bad. If your trigger is supposed to prevent a delete, make sure your test actually tries to delete a record and asserts that an error was thrown.
Security and Sharing
By default, triggers run in “system mode,” which means they ignore user permissions. This is great for some things, but dangerous for others. Use the “with sharing” keyword in your handler classes unless you have a very specific reason not to. You want to make sure you aren’t accidentally giving a user access to data they shouldn’t see just because a trigger ran.
Key Takeaways
- Stick to one trigger per object and use a handler class for the heavy lifting.
- Never put SOQL or DML inside a loop – always bulkify your logic.
- Use static variables to prevent recursive triggers from firing infinitely.
- Move long-running or external integration work to asynchronous methods.
- Avoid hardcoding IDs; use Custom Metadata instead.
- Implement a logging framework to catch and track errors in production.
Building triggers this way might take a little more time upfront, but it saves you hours of technical debt later. These Apex Trigger Best Practices are the foundation of a stable Salesforce environment. At the end of the day, your goal is to build something that the next developer won’t hate you for. Keep your code clean, your logic separated, and your tests meaningful, and your org will stay healthy as it grows.








Leave a Reply