An Apex trigger runs custom Apex code before or after Salesforce records are inserted, updated, deleted, or restored. This tutorial explains how to create an Apex trigger for the Contact object in Developer Console, select the required trigger events, add basic logic, and avoid common trigger mistakes.
Create an Apex Trigger in Salesforce Developer Console
An Apex trigger is associated with a specific Salesforce object, such as Account, Contact, Opportunity, or a custom object. The trigger executes automatically when one of its configured data events occurs.
In this tutorial, we create an Apex trigger for the Contact object. The example uses a before insert event, but the same creation process can be used for other supported events.
To create an Apex Trigger from Salesforce Developer Console, follow the steps below.
1. Open Salesforce Developer Console
Log in to Salesforce and open Developer Console from the user menu or setup menu available in your Salesforce interface.

Developer Console provides editors and tools for Apex classes, Apex triggers, SOQL queries, debug logs, and test execution.
2. Select New Apex Trigger
In Developer Console, click File, point to New, and select Apex Trigger.

3. Enter the Apex Trigger Name and Select Contact
After selecting Apex Trigger, Salesforce displays a dialog asking for the trigger name and the sObject on which the trigger should run.

Enter a descriptive name such as ValidateEmailTrigger. Select Contact as the sObject because the example trigger will run when Contact records are created.
We need to provide a name to the Apex Trigger and also select a sObject on which we are going to set the trigger.

Click Submit to create the trigger.
4. Review the Generated Apex Trigger Code
A new Apex Trigger is created with the following default code in a new window of Developer Console.
trigger ValidateEmailTrigger on Contact (before insert) {
}
The trigger declaration contains three main parts:
ValidateEmailTriggeris the trigger name.Contactis the Salesforce object associated with the trigger.before insertis the trigger event.
Developer Console initially creates this trigger with the before insert event. You can change the event list to match the business requirement.
Apex Trigger Events Available in Salesforce
A trigger can run for one or more record events. The supported events include:
before insertbefore updatebefore deleteafter insertafter updateafter deleteafter undelete
Before triggers are generally used to validate or modify field values before Salesforce saves the record. After triggers are used when logic requires a saved record ID, related-record processing, or access to values committed by the database operation.
Apex Trigger Syntax for Multiple Events
A single trigger can listen for multiple events by listing them inside parentheses.
trigger TriggerName on ObjectName (
before insert,
before update,
after insert,
after update
) {
// Trigger logic
}
Add Contact Validation Logic to the Apex Trigger
The following example prevents a Contact from being inserted when the Email field is blank. It processes every record in Trigger.new, so it works for both single-record and bulk operations.
trigger ValidateContactEmail on Contact (before insert) {
for (Contact contactRecord : Trigger.new) {
if (String.isBlank(contactRecord.Email)) {
contactRecord.Email.addError('Email is required.');
}
}
}
The addError() method associates an error with the Email field and prevents the record from being saved. Field requirements that can be implemented with standard configuration should usually be handled with required-field settings or validation rules before using Apex.
Apex Trigger Context Variables
Salesforce provides context variables that identify the current event and expose the affected records. Common trigger context variables include:
Trigger.new: New versions of records in insert and update operations.Trigger.old: Previous record versions in update and delete operations.Trigger.newMap: Map of record IDs to new record versions where IDs are available.Trigger.oldMap: Map of record IDs to previous record versions.Trigger.isInsert: Returnstrueduring an insert event.Trigger.isUpdate: Returnstrueduring an update event.Trigger.isBefore: Returnstruein a before trigger.Trigger.isAfter: Returnstruein an after trigger.Trigger.size: Number of records in the current trigger execution.
After Update Apex Trigger Example
The following example detects Contacts whose Email value changed during an update. It demonstrates the use of Trigger.new and Trigger.oldMap.
trigger ContactEmailChangeTrigger on Contact (after update) {
Set<Id> changedContactIds = new Set<Id>();
for (Contact currentContact : Trigger.new) {
Contact previousContact = Trigger.oldMap.get(currentContact.Id);
if (currentContact.Email != previousContact.Email) {
changedContactIds.add(currentContact.Id);
}
}
System.debug('Contacts with changed email: ' + changedContactIds);
}
A production trigger would normally pass these records to a handler class or service method instead of keeping substantial business logic inside the trigger body.
Bulk-Safe Apex Trigger Design
Salesforce can execute a trigger for many records in one transaction. A trigger must therefore handle collections rather than assuming that only one record is present.
- Process all records in
Trigger.neworTrigger.old. - Do not place SOQL queries inside record-processing loops.
- Do not place DML statements inside loops.
- Collect record IDs in sets and retrieve related records with one query.
- Collect records to insert, update, or delete and perform one DML operation per collection.
Bulkified Apex Trigger Query Pattern
trigger ContactAccountTrigger on Contact (before insert, before update) {
Set<Id> accountIds = new Set<Id>();
for (Contact contactRecord : Trigger.new) {
if (contactRecord.AccountId != null) {
accountIds.add(contactRecord.AccountId);
}
}
Map<Id, Account> accountsById = new Map<Id, Account>([
SELECT Id, Name
FROM Account
WHERE Id IN :accountIds
]);
for (Contact contactRecord : Trigger.new) {
Account relatedAccount = accountsById.get(contactRecord.AccountId);
if (relatedAccount != null) {
contactRecord.Description = 'Account: ' + relatedAccount.Name;
}
}
}
Use an Apex Trigger Handler Class
A common design is to keep the trigger body small and delegate business logic to a separate Apex class. This makes the logic easier to test, reuse, and maintain.
trigger ContactTrigger on Contact (before insert, before update) {
ContactTriggerHandler.validateEmails(Trigger.new);
}
public class ContactTriggerHandler {
public static void validateEmails(List<Contact> contacts) {
for (Contact contactRecord : contacts) {
if (String.isBlank(contactRecord.Email)) {
contactRecord.Email.addError('Email is required.');
}
}
}
}
Many Salesforce teams also follow a one-trigger-per-object convention so that event order and trigger behavior remain easier to manage.
Test the Salesforce Apex Trigger
Apex triggers should be covered by test methods that create their own test data and verify expected results. The following example checks that the Contact email validation prevents an invalid insert.
@IsTest
private class ValidateContactEmailTest {
@IsTest
static void contactWithoutEmailIsRejected() {
Contact contactRecord = new Contact(LastName = 'Test Contact');
Test.startTest();
Database.SaveResult result = Database.insert(contactRecord, false);
Test.stopTest();
System.assertEquals(false, result.isSuccess());
System.assert(
result.getErrors()[0].getMessage().contains('Email is required.')
);
}
}
Tests should also cover successful records, bulk record lists, update scenarios, null values, and any relevant user-permission or sharing behavior.
When to Use Apex Triggers in Salesforce
Use an Apex trigger when the required record-processing logic cannot be implemented clearly with Salesforce declarative automation or standard field configuration.
An Apex trigger may be appropriate when you need complex record comparisons, advanced bulk processing, reusable Apex services, transaction-level control, or logic that is difficult to express with validation rules or Flow.
Before writing a trigger, evaluate whether the requirement can be handled with a required field, formula, validation rule, duplicate rule, record-triggered Flow, or another standard Salesforce feature. Avoid implementing the same business rule in both Flow and Apex unless the interaction is deliberately designed and tested.
Common Apex Trigger Mistakes
- Assuming the trigger receives only one record.
- Running SOQL or DML statements inside a loop.
- Writing all business logic directly in the trigger body.
- Updating the same records repeatedly and causing trigger recursion.
- Using an after trigger when values could be assigned directly in a before trigger.
- Failing to compare old and new values in update triggers.
- Creating multiple triggers on the same object without a clear execution strategy.
- Ignoring object permissions, field permissions, sharing requirements, or user context.
- Writing tests only for a single successful record.
Apex Trigger Creation FAQ
What is an Apex trigger in Salesforce?
An Apex trigger is Apex code that executes automatically before or after specified record events, such as insert, update, delete, or undelete, on a Salesforce object.
What is the difference between Apex and an Apex trigger?
Apex is Salesforce’s programming language. An Apex trigger is one mechanism that invokes Apex automatically in response to database record events. Apex can also be used in classes, asynchronous jobs, controllers, services, and other execution contexts.
When should a before insert Apex trigger be used?
Use a before insert trigger to validate incoming records or assign field values before Salesforce saves them. Changes made directly to records in Trigger.new do not require a separate update statement in a before trigger.
Can one Apex trigger handle both insert and update events?
Yes. A trigger can declare multiple events, such as before insert and before update. Use context variables such as Trigger.isInsert and Trigger.isUpdate when event-specific handling is required.
Why must an Apex trigger be bulkified?
Salesforce may pass many records to a trigger in one transaction. Bulkified code processes record collections efficiently and avoids exceeding governor limits for SOQL queries, DML statements, CPU time, and other transaction resources.
Apex Trigger Tutorial QA Checklist
- Confirm that the trigger is associated with the intended Salesforce object and events.
- Verify that all records in
Trigger.neworTrigger.oldare processed. - Check that no SOQL query or DML statement runs inside a record loop.
- Verify that update logic compares old and new field values when necessary.
- Confirm that trigger recursion and interactions with Flow or other automation have been considered.
- Ensure tests cover successful, failing, bulk, insert, and update scenarios relevant to the trigger.
- Check whether the requirement could be implemented more simply with validation rules, required fields, or record-triggered Flow.
Salesforce Apex Trigger Creation Summary
To create an Apex trigger, open Developer Console, select File > New > Apex Trigger, enter a trigger name, select the Salesforce object, and submit the form. Configure the required before or after events, keep the trigger bulk-safe, move substantial logic into a handler class, and verify the behavior with Apex tests.
TutorialKart.com