Techila
Techila/Insights/Apex Trigger in Salesforce: Syntax, Context Variables, Examples and Best Practices

Salesforce · 10 min read

Apex Trigger in Salesforce: Syntax, Context Variables, Examples and Best Practices

Category

Salesforce

Read time

10 min read

Author

Techila Salesforce Practice

Published

28 Dec 2020

Last updated

10 Nov 2026

An Apex trigger in Salesforce is code that runs before or after a record is inserted, updated, deleted or undeleted. This guide covers trigger syntax, the context variables, worked examples, bulk-safe patterns, testing, and when a record-triggered flow is the better choice.

An Apex trigger in Salesforce is a piece of Apex code attached to an object that runs automatically before or after records are inserted, updated, deleted or undeleted. Before triggers validate or change the record being saved; after triggers act on related records once the save is done. Use an Apex trigger when the logic is too complex, too high-volume or too transaction-sensitive for a record-triggered flow.

This guide was first published in 2020 as "What is Apex Trigger in Salesforce?". It has been rewritten for 2026 and now also covers trigger examples, common trigger scenarios and how triggers compare with Flow, which replaced Workflow Rules and Process Builder.

What is an Apex trigger?

A trigger listens for database events on one object, such as Account or a custom object, and runs your code when they happen. Salesforce's Trailhead module Get Started with Apex Triggers describes the two kinds: before triggers "update or validate record values before they're saved", and after triggers read system-set values such as Id or LastModifiedDate and change other records. The records that fire an after trigger are read-only.

The same module points out that many jobs don't need code at all: validation rules and flows cover simpler requirements, and triggers earn their place when performance, scale or complex logic demand it.

Apex trigger syntax and events

An Apex trigger is declared with the trigger keyword, a name, the object it runs on and the list of events that fire it. Every trigger follows the same shape:

trigger TriggerName on ObjectName (trigger_events) { code_block }

The events you can list are:

EventRunsTypical use
before insertBefore a new record is savedDefault or derive field values, block bad data
before updateBefore changes are savedRecalculate fields, validate a change against the old value
before deleteBefore a record is deletedStop deletion when related records exist
after insertAfter a new record is savedCreate related records that need the new Id
after updateAfter changes are savedUpdate child or parent records, queue integrations
after deleteAfter a record is deletedClean up or recalculate related data
after undeleteAfter a record is restored from the Recycle BinRestore related data or totals

One trigger can handle several events, for example trigger AccountTrigger on Account (before insert, before update, after insert, after update). The after undelete event is worth remembering: Salesforce's record-triggered automation guide notes that record-triggered flows don't support that context, so restore logic needs a trigger.

Trigger context variables

Inside a trigger, the Trigger class tells your code what is happening. The Trailhead trigger module lists them:

  • Booleans: isExecuting, isInsert, isUpdate, isDelete, isUndelete, isBefore, isAfter.
  • Records: Trigger.new and Trigger.newMap (the new versions), Trigger.old and Trigger.oldMap (the versions before the change). New values exist for insert, update and undelete; old values for update and delete.
  • Other: Trigger.operationType, an enum such as BEFORE_INSERT that works well in a switch on statement, and Trigger.size, the number of records in the current invocation.

Comparing Trigger.oldMap.get(rec.Id) with the new record is how you detect that a specific field changed, which keeps a trigger from doing work on every save.

Apex trigger examples

These short examples cover the scenarios people most often search for. All of them loop over the full record list, so they are safe for bulk loads.

1. Set a default value (before insert)

trigger AccountDefaults on Account (before insert) { for (Account a : Trigger.new) { if (a.Description == null) { a.Description = 'New account'; } } }

No DML is needed: in a before trigger you change the records directly. The Trailhead module warns that running DML on those same records causes an error.

2. Block a delete (before delete)

trigger AccountDeletion on Account (before delete) { for (Account a : [SELECT Id FROM Account WHERE Id IN (SELECT AccountId FROM Opportunity) AND Id IN :Trigger.old]) { Trigger.oldMap.get(a.Id).addError('Cannot delete an account with opportunities.'); } }

This mirrors Trailhead's own example. addError() stops the operation and shows the message to the user.

3. Create related records (after insert)

After an account is created, collect a new Opportunity for each one into a list inside the loop, then run a single insert after the loop. The account Id is only available in an after trigger, which is why this logic can't live in a before trigger.

4. React only when a field changes (after update)

Loop over Trigger.new, compare each record's Status with Trigger.oldMap, and only add changed records to the list you process. This is the usual fix for triggers that update child records on every save.

5. Call an external system

Triggers can't wait on a web service. Trailhead says that "the callout must be done asynchronously", for example by queueing a Queueable class that implements Database.AllowsCallouts with System.enqueueJob().

How to write bulk-safe triggers

Triggers don't fire once per record. Salesforce's Bulk Apex Triggers unit explains that records are processed in batches of 200, so a large data load fires the same trigger several times in one go. Governor limits apply to the whole transaction: 100 SOQL queries for synchronous Apex (200 for asynchronous) and 150 DML calls. Our governor limits guide lists the rest. The rules that follow from that:

  1. Always loop over the collection. Never read only Trigger.new[0].
  2. Query once, outside the loop. Use a filter such as WHERE AccountId IN :Trigger.new, then use the results inside the loop.
  3. Do DML once, outside the loop. Add records to a list and update or insert the whole list at the end.
  4. Use maps to match records. A Map<Id, Account> replaces nested loops.
  5. Test with many records. A trigger that passes with one record can fail with 200.

Trigger handler pattern and recursion

The best-practice structure is one trigger per object that only routes events, with the logic in Apex classes. Most teams keep the trigger itself to a few lines and move the logic into an Apex class, often called a handler. Trailhead notes that you can call public utility methods from a trigger, which gives reuse, smaller triggers and easier maintenance. A common shape is one trigger per object that does nothing but route each Trigger.operationType to a handler method.

  • Trigger: one per object, a switch on Trigger.operationType and nothing else.
  • Handler: one method per event, such as beforeInsert(List<Account> newRecords). An Apex interface can force every handler to offer the same methods.
  • Helper or service classes: the reusable business logic, which other classes and batch jobs can call too.
  • Bypass switch: a check at the top of the trigger against a hierarchy custom setting or custom permission, so an admin can switch the trigger off for a data load or an integration user without a deployment.

Where the trigger runs in the save sequence, relative to validation rules, flows and assignment rules, is set out in our order of execution guide.

That fits Salesforce's architect guidance: "For a given Salesforce Object, use one mechanism as your entry point into automation." With several triggers on one object you can't control the order they run in, and logic gets duplicated.

Recursion happens when an after-update trigger updates the same records, which fires the trigger again. Guard against it by updating only records whose relevant fields actually changed, and by moving same-record field changes into a before trigger, where no extra save is needed.

Apex trigger or Flow: which should you use?

Use a record-triggered flow for simple, low-volume automation that admins maintain, and an Apex trigger for complex, high-volume or transaction-sensitive logic. Older guides compare triggers with Workflow Rules and Process Builder. Those tools have been retired (see our Salesforce workflow guide): Salesforce's end-of-support article says support for both ended on 31 December 2025, existing rules keep running without bug fixes, and new automation should be built in Flow Builder, with the Migrate to Flow tool for existing rules. As of November 2026 the real comparison today is triggers against record-triggered flows.

QuestionRecord-triggered flowApex trigger
Who builds itAdmins, point and clickDevelopers, in code
Automation density on the objectLowHigh
Bulk handlingAutomatic bulkificationYour code must be bulk-safe
Transaction controlNo savepoints, rollbacks or partial-success DMLFull control, including error recovery
Scheduled pathsBuilt inNeeds async Apex
After undeleteNot supportedSupported

The architect guide recommends record-triggered flow for low automation density, a hybrid of flow plus invocable Apex in the middle, and Apex triggers for high-density automation. Whichever you choose, "try to avoid mixing Flow and Apex triggers as entry points for the same Object."

Testing and deploying triggers

Every trigger needs test coverage before it can reach production. Build and test triggers in a sandbox, then deploy them. According to Trailhead's Apex unit test module, at least 75% of Apex code must be covered by passing tests before you deploy or package it, and each trigger must have some coverage. Aim higher than the minimum: test inserts, updates and deletes, a failure case that hits addError(), a bulk case with a few hundred records, and one test per event that proves the trigger calls the right handler method. Our sandbox guide explains which sandbox type suits build and test work.

Common trigger mistakes

Most trigger failures come from code that works for one record and breaks in bulk or in combination with other automation.

  • SOQL or DML inside a loop: fine in a one-record test, a limit error in a data load.
  • Several triggers on one object: the run order isn't guaranteed, so behaviour becomes unpredictable.
  • Doing work on every save: check that the fields you care about actually changed.
  • Synchronous callouts: move them to Queueable Apex.
  • A trigger where a flow would do: a simple field default is easier for admins to maintain as a before-save flow, as long as the object isn't already automated with triggers. Our Salesforce Flow guide shows how to build one.

Getting help

Techila is a Salesforce Summit Partner. Our Salesforce development services team writes, reviews and refactors Apex triggers, and consolidates mixed trigger and flow automation into one pattern per object. Talk to us about a trigger review or a migration from legacy automation.

Frequently asked questions

What is an Apex trigger in Salesforce?

An Apex trigger is Apex code attached to an object that runs automatically before or after records are inserted, updated, deleted or undeleted. Developers use it for logic that validation rules and flows can't handle well, such as complex cross-object updates, high-volume processing or fine-grained transaction control.

What is the difference between a before trigger and an after trigger?

A before trigger runs before the record is saved, so you can change or validate its field values without a DML statement. An after trigger runs once the record is saved, so system values such as the record Id are available; the triggering records are read-only, and it is used to change other, related records.

How many records does an Apex trigger process at once?

Triggers process records in batches of 200. A data load of 1,000 records fires the trigger five times, which is why trigger code must loop over Trigger.new and keep SOQL queries and DML statements outside the loop.

Should I use an Apex trigger or a record-triggered flow?

Salesforce's architect guide recommends record-triggered flows for objects with low automation density and Apex triggers for high-density automation, bulk-heavy work and transaction control. Pick one entry point per object and avoid mixing flows and triggers on the same object.

Do Apex triggers need test classes?

Yes. To deploy Apex to production, at least 75% of your Apex code must be covered by passing tests, and each trigger must have some coverage. Good tests also cover bulk cases, not just a single record.

More perspectives.

Let's now look at how to build
the next 10 years of
your customer platform.

See case studies