Duplicate records are one of the biggest threats to maintaining clean, reliable CRM data. When the same Contact exists multiple times with identical information—especially key fields like Email, Last Name, and Phone—it can result in broken communication flows, poor customer experience, inaccurate reporting, and wasted sales effort.
In this blog, we’ll walk through a before insert Apex trigger in Salesforce that checks for existing Contacts with the same Email, Last Name, and Phone values. If such a match is found, the new Contact is blocked from being saved with a friendly validation error:
“Duplicate Found”.
This solution goes beyond standard Salesforce duplicate rules by giving you full control using Apex logic. You’ll learn how to query existing records, compare values, and raise errors—all while keeping the code bulk-safe and easy to read.
🧠 Why Duplicate Contacts Are a Problem
Whether your team is importing contacts from spreadsheets, creating them manually, or syncing them via integrations, duplicate entries are bound to occur—especially when validations aren’t enforced. While Salesforce provides out-of-the-box duplicate management, those tools can be limited in flexibility.
Apex gives you the ability to:
-
Customize how duplicates are defined (e.g., based on combinations of fields)
-
Control when and how the validation is enforced (e.g., only on insert, or insert & update)
-
Provide real-time feedback to users using
addError()
This solution is ideal for any business that wants tight control over data quality and zero tolerance for duplicate records slipping into the system.
🔍 What You’ll Learn in This Blog
-
How to write a before insert Apex trigger to prevent duplicate Contacts
-
How to use SOQL to query existing records and match against new entries
-
How to compare field values across multiple conditions
-
How to use
addError()
to stop duplicate records from being saved -
Why Apex can be more powerful than standard matching rules
-
How to build a scalable and readable handler-based trigger framework
⚙️ How the Trigger Works – Step-by-Step
The logic sits inside a method in the ContactTriggerHandler
class, triggered during the before insert phase of Contact creation.
Here’s how it works:
-
A SOQL query fetches up to 50,000 existing Contact records, retrieving their Email, Phone, and Last Name.
-
For each new Contact being inserted, the trigger loops through the existing records.
-
It checks if the new record’s Email, Last Name, and Phone exactly match any of the existing ones.
-
If a match is found, the trigger uses
addError()
on the Contact to stop the insert and inform the user.
This ensures that only unique Contacts make it into your system—even if the user attempts to insert duplicates via bulk upload or API integration.
🎯 Use Cases for Multi-Field Duplicate Checks
-
Sales and Marketing teams working from shared lead lists or import files
-
Customer service teams who create new Contacts during support cases
-
Third-party integrations that sync contacts from external platforms
-
Businesses in healthcare, education, or finance, where contact duplication can lead to compliance issues
This approach is also a fantastic learning opportunity for developers and admins aiming to build custom validations that scale.
👨💻 Developer & Admin Tips
-
Modify the SOQL query to include filters (e.g.,
WHERE IsDeleted = false
) to reduce data load -
Use nested Maps or Sets to improve performance for large datasets
-
Replace hardcoded field combinations with Custom Metadata to make validation configurable
-
Add test classes with both positive and negative test scenarios
-
Make the error message more user-friendly by showing which field caused the match
🎥 Learn with Hands-On Tutorials – YouTube Playlist Included
We’ve added a YouTube playlist that walks you through:
-
Writing your first multi-field duplicate checker in Apex
-
Testing it in your Salesforce developer org
-
Exploring ways to optimize and expand the logic
The “Salesforce Makes Sense” style makes this topic approachable, even if you’re new to triggers or Apex in general.
Solution:
public class ContactTriggerHandler {
public static void handleBeforeInsert (List<Contact> newRecords) {
List<Contact> existingRecords = [SELECT Id, LastName, Phone, Email FROM
Contact LIMIT 50000];
for (Contact con: newRecords) {
for (Contact existingCon : existingRecords) {
if (con.LastName == existingCon.LastName && con.Email == existingCon.
Email && con.Phone == existingCon.Phone){
con.addError(‘Duplicate Found’);
}
}
}
}
}