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

In Salesforce, triggers are powerful tools that allow developers to execute custom logic before or after records are inserted, updated, deleted, or undeleted. However, triggers run in system context by default, which means they can bypass a user’s CRUD (Create, Read, Update, Delete) permissions and Field-Level Security (FLS) restrictions if not handled carefully.
This behavior can lead to serious security issues where users are indirectly allowed to read or modify data they should not have access to. Therefore, ensuring that triggers respect CRUD and FLS rules—especially when running in different execution contexts such as UI actions, batch jobs, integrations, or future methods—is a critical best practice in Salesforce development.
This article explains how to enforce CRUD and FLS compliance in triggers, why it matters, and how to implement it using best-practice patterns and Apex code examples.
Understanding CRUD and FLS in Salesforce
Before implementing security checks, it is important to understand what CRUD and FLS mean.
CRUD (Object-Level Security)
CRUD determines whether a user can:
- Create records
- Read records
- Update records
- Delete records
These permissions are set at the object level using profiles or permission sets.
FLS (Field-Level Security)
FLS controls whether a user can:
- See (read) specific fields
- Edit (write) specific fields
Even if a user has access to an object, they may not have access to all fields on that object.
Why Triggers Can Violate CRUD and FLS
Triggers run in system mode, not user mode. This means:
- Object permissions are ignored
- Field-level security is ignored
- Code executes with elevated privileges
For example, a trigger can update a field even if the running user does not have permission to edit that field. This is useful in some automation scenarios, but dangerous if not controlled properly.
Because triggers can be invoked from:
- UI actions
- Data Loader
- API integrations
- Batch Apex
- Scheduled Apex
- Future methods
…it becomes even more important to explicitly enforce security checks.
Key Principles to Ensure CRUD and FLS Compliance
To ensure triggers do not violate security rules, follow these principles:
- Never put business logic directly in triggers
- Perform CRUD checks before DML
- Perform FLS checks before reading or updating fields
- Use Security.stripInaccessible()
- Centralize security logic in helper classes
- Respect different execution contexts
- Write test cases for limited-access users
Recommended Trigger Pattern
A trigger should only act as a dispatcher and delegate logic to a handler class.
Example Trigger
trigger AccountTrigger on Account (
before insert, before update,
after insert, after update
) {
if (Trigger.isBefore) {
if (Trigger.isInsert || Trigger.isUpdate) {
AccountTriggerHandler.beforeSave(Trigger.new);
}
}
if (Trigger.isAfter) {
if (Trigger.isInsert || Trigger.isUpdate) {
AccountTriggerHandler.afterSave(Trigger.new);
}
}
}
JavaScriptThis approach keeps triggers clean and makes it easier to enforce security rules in one place.
Enforcing CRUD Permissions
Checking Object-Level Access
Before performing any DML operation, verify that the user has the required permissions.
Example: CRUD Check
if (!Schema.sObjectType.Account.isUpdateable()) {
throw new AuthorizationException('You do not have permission to update Account records.');
}
JavaScriptCommon CRUD checks:
Schema.sObjectType.Account.isCreateable();
Schema.sObjectType.Account.isAccessible();
Schema.sObjectType.Account.isUpdateable();
Schema.sObjectType.Account.isDeletable();
JavaScriptEnforcing Field-Level Security (FLS)
Checking Field Permissions Manually
You can check FLS using DescribeFieldResult.
Example
Schema.DescribeFieldResult fieldDesc =
Account.AnnualRevenue.getDescribe();
if (!fieldDesc.isUpdateable()) {
throw new AuthorizationException('No access to update Annual Revenue.');
}
JavaScriptWhile effective, this approach becomes verbose when handling many fields.
Best Practice: Using Security.stripInaccessible()
Salesforce provides Security.stripInaccessible() to automatically enforce FLS and CRUD in Apex.
Why Use stripInaccessible()?
- Automatically removes fields the user cannot access
- Prevents accidental FLS violations
- Cleaner and safer code
- Recommended by Salesforce
Example: FLS-Safe Update Using stripInaccessible()
public class AccountTriggerHandler {
public static void beforeSave(List<Account> newAccounts) {
// CRUD check
if (!Schema.sObjectType.Account.isUpdateable()) {
return;
}
// Strip inaccessible fields
SObjectAccessDecision decision =
Security.stripInaccessible(
AccessType.UPDATABLE,
newAccounts
);
List<Account> safeAccounts =
(List<Account>) decision.getRecords();
for (Account acc : safeAccounts) {
if (acc.AnnualRevenue != null && acc.AnnualRevenue > 1000000) {
acc.Description = 'High value account';
}
}
}
}
JavaScriptThis ensures:
- Only updateable fields are modified
- Fields without access are ignored
- No runtime security violation occurs
Handling Read Access (FLS + CRUD)
Triggers often read fields for logic. Even reading restricted fields can violate FLS.
Example: Read-Safe Logic
if (!Schema.sObjectType.Account.isAccessible()) {
return;
}
Schema.DescribeFieldResult desc =
Account.Industry.getDescribe();
if (desc.isAccessible()) {
for (Account acc : Trigger.new) {
System.debug(acc.Industry);
}
}
JavaScriptContext-Specific Considerations
1. UI-Initiated Triggers
When users edit records from the UI:
- Respect both CRUD and FLS strictly
- Use
stripInaccessible() - Avoid forcing updates to restricted fields
2. Batch Apex and Scheduled Jobs
Batch jobs often run as an integration or admin user:
- Decide whether system-level access is intentional
- Still use FLS enforcement for shared code
- Document exceptions clearly
3. Integration Context (API)
Triggers invoked via API can bypass UI validations:
- Always enforce CRUD/FLS
- Validate incoming data
- Avoid blind updates
Using with Sharing vs without Sharing
Triggers themselves do not support sharing keywords, but handler classes do.
public with sharing class AccountTriggerHandler {
// respects record-level sharing
}
JavaScriptImportant:
with sharingenforces record-level access- It does NOT enforce CRUD or FLS
- CRUD/FLS must still be checked explicitly
Avoiding Common Mistakes
❌ Assuming triggers automatically enforce security
❌ Hardcoding admin-level updates
❌ Skipping FLS checks for “internal” fields
❌ Writing logic directly in triggers
❌ Not testing with restricted users
Writing Secure Test Classes
Always test triggers using users with limited access.
Example Test Setup
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',
Alias = 'tuser',
TimeZoneSidKey = 'Asia/Kolkata',
LocaleSidKey = 'en_IN',
EmailEncodingKey = 'UTF-8',
LanguageLocaleKey = 'en_US'
);
System.runAs(u) {
Account acc = new Account(Name='Test');
insert acc;
}
JavaScriptThis ensures your trigger logic works correctly under restricted permissions.
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?
