How the Salesforce merge trigger handles records
Ever sat there staring at your debug logs, trying to figure out why your code ran three times instead of one? If you’re working with a Salesforce merge trigger, the execution count can feel a bit like a guessing game if you don’t know the rules. I’ve seen teams spend hours debugging side effects because they assumed a merge was just a simple update.
Here’s the thing: Salesforce doesn’t have a specific “merge” trigger context. Instead, it repurposes the tools it already has. When you merge records, the system treats the “master” record as an update and the “losing” records as deletes. It sounds simple, but it’s probably the most overlooked feature when people are building out their handler frameworks.
So, what does this actually mean for your code? It means your logic is going to fire multiple times across different contexts for a single user action. If you’re curious about how this compares to other complex events, you might want to check out my post on how many times an upsert trigger executes to see the patterns.
Breaking down the execution count
The math is actually pretty consistent once you see it. Let’s say you’re merging three records into one master. You’ll have one master record and two losing records. Here is how the trigger fires:
- The Delete Context: This runs for the losing records. In our example, the delete trigger fires for the two records being removed.
- The Update Context: This runs once for the master record because its fields are being updated with data from the losers.
So, for a 3-record merge, you’re looking at two delete executions and one update execution. If you merge N records, you’ll get N-1 delete executions and 1 update execution. Total invocations? That’s N triggers firing in various contexts. Simple, right? But it’s exactly where people get tripped up.
Best practices for your Salesforce merge trigger
When I first worked with merges, I expected some kind of special isMerge flag. But guess what? It doesn’t exist. You have to infer that a merge is happening by looking at the state of the records. Honestly, most teams get this wrong because they don’t account for the delete and update happening in the same transaction.
Pro tip: Since there is no Trigger.isMerge, you need to be extra careful with your static variables. If you use a simple “hasRun” boolean to stop recursion, you might accidentally block the update trigger from running after the delete trigger finishes.
One thing that trips people up is the reparenting of child records. When you merge, all those related contacts or opportunities get moved to the master record. This can fire even more triggers on those child objects. If you’re doing Salesforce lead merging, the data movement can get even more complex with tasks and events shifting around.
Writing merge-aware code
You’ll want to keep your logic separated. Don’t try to cram everything into a single method. Use your handler to route the records based on the context. If you don’t, you might end up trying to update a record that’s already been marked for deletion, and that’s a one-way ticket to a “Record Deleted” exception.
Look at this basic pattern for your handler:
if (Trigger.isBefore && Trigger.isDelete) {
// This is where your losing records land.
// Handle any cleanup before they vanish.
}
if (Trigger.isAfter && Trigger.isUpdate) {
// This is where the winner lives.
// The master record has the new merged data here.
}By keeping these separate, you ensure that your “losing” logic doesn’t interfere with your “winning” logic. It’s all about making your code idempotent. You want to make sure that no matter how many times the trigger fires, the end result is exactly what you intended.
Key Takeaways
- A Salesforce merge trigger isn’t a single event; it’s a combination of delete and update contexts.
- The master record always triggers an update (before and after).
- Every losing record triggers a delete (before and after).
- There is no Trigger.isMerge flag, so you have to handle logic within the standard update/delete blocks.
- Child record reparenting can cause a chain reaction of triggers on other objects.
Knowing exactly how many times your code runs is the difference between a clean org and a mess of data inconsistencies. Next time you’re building a trigger handler, take a second to think about the losers in a merge. Your future self will thank you when you aren’t chasing down ghost bugs in the middle of the night. Use separate methods for your logic, watch your static variables, and always test with multiple records to make sure your bulk logic holds up.








Leave a Reply