How do you ensure the trigger does not violate CRUD or FLS rules when run in different contexts?

This answer is written in a Salesforce interview + developer-guide style, with clear explanations, best practices, and code examples.
Introduction
In Salesforce, Apex triggers run automatically in response to database events such as insert, update, delete, or undelete. While triggers are powerful, they also pose a major security risk if not written carefully.
One of the most common mistakes developers make is ignoring CRUD (Create, Read, Update, Delete) and FLS (Field-Level Security) when triggers execute in different user contexts. This can result in:
- Unauthorized data access
- Security review failures
- Runtime exceptions
- Compliance issues
Therefore, ensuring that Apex triggers respect CRUD and FLS rules is a critical best practice in Salesforce development.
This article explains how to ensure triggers do not violate CRUD or FLS, even when executed by users with different permissions, using modern Salesforce tools, patterns, and code examples.
Understanding CRUD and FLS in Salesforce
What Is CRUD?
CRUD refers to object-level permissions:
- Create – Can the user create records?
- Read – Can the user view records?
- Update – Can the user modify records?
- Delete – Can the user delete records?
Each Salesforce profile or permission set defines CRUD access per object.
What Is FLS (Field-Level Security)?
FLS controls field-level access:
- Can a user read a field?
- Can a user edit a field?
Even if a user can update an object, they may not have permission to edit certain fields.
Why Triggers Are Risky
Apex triggers traditionally run in system context, which means:
- They ignore object permissions
- They ignore field-level security
If not handled carefully, a trigger can update restricted fields or objects without user authorization.
Why You Must Enforce CRUD and FLS in Triggers
Failing to enforce security can cause:
- Security Review Rejection (AppExchange apps)
- Data leaks
- Unexpected behavior across user profiles
- Runtime errors for restricted users
- Violation of Salesforce security best practices
Salesforce strongly recommends that any Apex that performs DML or queries must explicitly check CRUD and FLS.
Golden Rule: Triggers Should Be Thin
Best Practice:
Triggers should only delegate logic to handler classes.
This allows:
- Centralized security checks
- Easier testing
- Reusable logic
- Cleaner code
Step 1: Use Trigger Handler Pattern
Example Trigger (Thin Trigger)
trigger AccountTrigger on Account (before insert, before update) {
AccountTriggerHandler.handleBeforeInsertUpdate(Trigger.new);
}
JavaScriptThe trigger does nothing except delegate logic.
Step 2: Check CRUD Permissions Using Schema Methods
Object-Level CRUD Check
Salesforce provides Schema.sObjectType methods to check permissions.
if (!Schema.sObjectType.Account.isCreateable()) {
throw new AuthorizationException('You do not have permission to create Account records.');
}
JavaScriptCommon CRUD Methods
Schema.sObjectType.Account.isCreateable();
Schema.sObjectType.Account.isReadable();
Schema.sObjectType.Account.isUpdateable();
Schema.sObjectType.Account.isDeletable();
JavaScriptStep 3: Enforce Field-Level Security (FLS)
Checking Field Permissions
if (!Schema.sObjectType.Account.fields.AnnualRevenue.isUpdateable()) {
throw new AuthorizationException('No permission to update Annual Revenue.');
}
JavaScriptThis ensures the trigger does not modify restricted fields.
Step 4: Use Security.stripInaccessible() (Recommended)
Salesforce introduced Security.stripInaccessible() to automatically enforce FLS and CRUD.
Why Use stripInaccessible?
- Automatically removes inaccessible fields
- Prevents security violations
- Cleaner and safer than manual checks
- Salesforce-recommended approach
Example: Secure Before Insert / Update Logic
public class AccountTriggerHandler {
public static void handleBeforeInsertUpdate(List<Account> newAccounts) {
// CRUD check
if (!Schema.sObjectType.Account.isCreateable() &&
!Schema.sObjectType.Account.isUpdateable()) {
throw new AuthorizationException('Insufficient permissions on Account.');
}
// Enforce FLS
SObjectAccessDecision decision =
Security.stripInaccessible(
AccessType.UPDATABLE,
newAccounts
);
List<Account> sanitizedAccounts =
(List<Account>) decision.getRecords();
for (Account acc : sanitizedAccounts) {
if (acc.Name != null) {
acc.Name = acc.Name.trim();
}
}
}
}
JavaScriptWhat Happens Here?
- Restricted fields are automatically removed
- Only accessible fields are processed
- Prevents runtime security errors
Step 5: Secure SOQL Queries in Triggers
Bad Practice (Insecure Query)
List<Account> accounts = [SELECT Name, AnnualRevenue FROM Account];
JavaScriptThis ignores FLS.
Secure Query Using WITH SECURITY_ENFORCED
List<Account> accounts =
[SELECT Name, AnnualRevenue FROM Account
WITH SECURITY_ENFORCED];
JavaScriptIf the user lacks access, Salesforce throws a runtime exception instead of exposing data.
Step 6: Handle Mixed User Contexts
Triggers may run in multiple contexts:
- Standard users
- Admins
- Community users
- Integration users
- Automated processes
Best Practice
- Never assume admin access
- Always validate permissions dynamically
- Use least-privilege principles
Step 7: Use Custom Exceptions Gracefully
Instead of silent failures, provide meaningful error messages.
public class AuthorizationException extends Exception {}
JavaScriptif (!Schema.sObjectType.Contact.isUpdateable()) {
throw new AuthorizationException(
'You do not have permission to update Contact records.'
);
}
JavaScriptThis helps users and admins understand failures clearly.
Step 8: Avoid Hardcoding Field Access
Bad Practice
acc.Secret_Field__c = 'Value';
JavaScriptGood Practice
if (Schema.sObjectType.Account.fields.Secret_Field__c.isUpdateable()) {
acc.Secret_Field__c = 'Value';
}
JavaScriptStep 9: Centralize Security Checks in Utility Class
Security Utility Class
public class SecurityUtil {
public static void checkUpdateAccess(Schema.SObjectType objType) {
if (!objType.isUpdateable()) {
throw new AuthorizationException('Update access denied.');
}
}
public static void checkFieldUpdate(
Schema.SObjectField field
) {
if (!field.getDescribe().isUpdateable()) {
throw new AuthorizationException('Field update denied.');
}
}
}
JavaScriptUsage in Trigger Handler
SecurityUtil.checkUpdateAccess(Account.SObjectType);
SecurityUtil.checkFieldUpdate(Account.AnnualRevenue);
JavaScriptStep 10: Writing Secure Test Classes
Test with Different Profiles
@isTest
private class AccountTriggerSecurityTest {
static testMethod void testRestrictedUser() {
Profile p = [
SELECT Id FROM Profile WHERE Name = 'Standard User'
];
User u = new User(
ProfileId = p.Id,
Username = 'testuser@example.com',
Email = 'testuser@example.com',
LastName = 'User',
Alias = 'tuser',
TimeZoneSidKey = 'Asia/Kolkata',
LocaleSidKey = 'en_US',
EmailEncodingKey = 'UTF-8',
LanguageLocaleKey = 'en_US'
);
insert u;
System.runAs(u) {
Account acc = new Account(Name = 'Test');
insert acc; // Should respect CRUD/FLS
}
}
}
JavaScriptThis validates trigger behavior under restricted permissions.
Common Mistakes to Avoid
❌ Assuming triggers always run as admin
❌ Ignoring FLS for updates
❌ Writing SOQL without security enforcement
❌ Hardcoding field access
❌ Skipping permission checks in helper classes
Salesforce Best Practices Summary
✔ Use Trigger Handler Pattern
✔ Always check CRUD before DML
✔ Always enforce FLS before field access
✔ Use Security.stripInaccessible()
✔ Use WITH SECURITY_ENFORCED in SOQL
✔ Test with non-admin users
✔ Centralize security logic
Interview-Ready One-Line Answer
“To ensure Apex triggers do not violate CRUD or FLS rules, I delegate logic to handler classes, validate object permissions using Schema methods, enforce field-level security with Security.stripInaccessible(), secure SOQL queries using WITH SECURITY_ENFORCED, and test behavior across different user contexts.”
Conclusion
Ensuring that Apex triggers respect CRUD and FLS rules is not optional—it is a core responsibility of every Salesforce developer. Triggers that ignore security may work technically, but they introduce serious risks.
By following Salesforce-recommended patterns, using built-in security utilities, and testing under real user contexts, you can write secure, scalable, and compliant Apex triggers that behave correctly in every situation.
Related Posts

How to Automatically create a follow-up Task when a Lead is converted

How You need to update a related child record whenever a parent record’s status changes, but only if the status is “Closed Won.” How would you design this in Apex?
