Skip to main content
New tool CRON Expression Builder — preview next run times before you schedule Apex. Open the builder →
Diagram illustrating the execution sequence of a Salesforce upsert trigger for bulk data loading.
Apex

Salesforce Upsert Trigger - How Many Times Does It Fire?

Does an upsert fire both the insert and the update trigger at the same time? It is a common interview question that trips up even senior developers, and the answer comes down to how the platform routes each record in a bulk load.

The short answer

A Salesforce upsert trigger runs twice per record: once in the before context and once in the after context. Each record goes down either the insert path or the update path, never both in the same transaction.

Key takeaways In an upsert, a single record runs through insert contexts or update contexts, never both. Each record runs twice per upsert DML operation: once before the save and once after it. A bulk upsert that mixes new and existing records fires both insert and update contexts inside the same transaction. Check Trigger.isInsert and Trigger.isUpdate in your handler classes so update logic does not silently skip the inserted records.

How the Salesforce upsert trigger handles records

If you've been working in the ecosystem for a while, you've probably had to explain how a Salesforce upsert trigger actually works. It's one of those classic "gotcha" questions that pops up in senior dev interviews and architectural reviews alike, and it's also a practical reality every time you sync data from an external system.

The confusion usually starts when people try to figure out if a record counts as an insert, an update, or both. I've seen teams over-complicate their code because they were afraid a record would fire through every single trigger context in one go.

The trigger executes twice per record: once in the before context and once in the after context. It only fires for the operation that actually happens. If the record is new, it's an insert. If it exists, it's an update. It is never both at the same time for the same record.

A flow diagram showing the logic branch between an insert and an update operation in a database system.

A flow diagram showing the logic branch between an insert and an update operation in a database system.

Decoding the Salesforce upsert trigger execution flow

When you call an upsert, Salesforce does a quick check against the ID or the External ID you've provided. It's making a split-second decision for every record in your list. That's why the topic keeps showing up in Apex trigger interview questions: it tests whether you understand the underlying DML logic.

The platform splits the work two ways:

  • If the record is brand new, the trigger fires in before insert and after insert. The update contexts are completely ignored for these records.
  • If the record matches an existing ID, the trigger fires in before update and after update. The insert contexts never see these records.

So if you're doing a bulk upsert with 200 records and 100 are new while 100 are existing, Salesforce is smart enough to group them. Your trigger fires for the insert context (for the new guys) and the update context (for the existing ones) within that same transaction. For any single individual record, though, it's a one-way street.

Always check Trigger.isInsert or Trigger.isUpdate inside your handler classes. I've seen developers write generic logic that they thought would run for both, only to realize their "Update" logic wasn't touching the "Inserted" records during a massive data load.

A simple code pattern for upserts

I usually tell my junior devs to stick to a clean handler pattern. You don't want to guess what's happening under the hood. When you're deciding between Apex vs Flow for these operations, remember that triggers give you this granular control over the insert/update split that's hard to replicate elsewhere.

trigger AccountTrigger on Account (before insert, after insert, before update, after update) {
    if (Trigger.isBefore) {
        if (Trigger.isInsert) {
            // This only hits for brand new records
        } else if (Trigger.isUpdate) {
            // This only hits for existing records being matched
        }
    }
}

Debugging a Salesforce upsert trigger in the wild

One thing that trips people up is the "twice" phrasing. When someone says a trigger executes twice, they mean the before and after phases. They don't mean the record is being saved to the database twice. It's a single DML event for each record.

Bulk scenarios work the same way. If you're pushing 500 records, Salesforce chunks them into batches of 200. In each batch, the Salesforce upsert trigger runs its phases. If a batch contains a mix of inserts and updates, the trigger enters the insert blocks for the new records and the update blocks for the existing ones. It's efficient, but you have to keep your collections straight.

Key takeaways

  • The Salesforce upsert trigger never treats a single record as both an insert and an update in one transaction.
  • Each record fires exactly twice: once before the save and once after.
  • Bulk upserts can trigger both insert and update contexts in the same transaction if the record set is mixed.
  • Always use context variables like Trigger.isInsert to keep your logic from stepping on its own toes.

Understanding the behavior comes down to knowing how the platform identifies data. Whether you're using a standard ID or a custom External ID, the trigger follows the same rules. Keep your logic modular and keep the bulk case in mind, and the next big data migration shouldn't surprise you.

Frequently asked questions

How many times does a trigger fire on upsert in Salesforce?

Twice for each upserted record: once in the before context and once in the after context. It runs only for the operation Salesforce picks, insert for new records and update for existing ones.

Does an upsert fire both insert and update triggers in Salesforce?

No. A single record never fires both during the same upsert transaction. Salesforce checks the record ID or External ID and sends the record to the insert contexts or to the update contexts, not both.

How does Salesforce handle bulk upserts with mixed new and existing records?

Salesforce groups the records inside the batch and fires the trigger for both insert and update contexts in that transaction. The insert blocks handle the new records and the update blocks handle the existing ones.

Newsletter

One email every Tuesday

New guides, tool updates, and the release-note changes that break things.

No spam. Unsubscribe in one click.

Comments

Loading comments...

Leave a Comment