Getting Started with the Trigger Framework - j-fischer/rflib GitHub Wiki
The RFLIB Trigger Framework provides a metadata-driven approach to managing Salesforce triggers, offering:
- Decoupled design: Separate trigger logic from trigger declarations
- Configurable activation: Enable/disable handlers without code changes
- Error handling: Built-in logging and exception management
- Recursion tracking: Prevent infinite trigger loops
- Order control: Define execution sequence for multiple handlers
- RFLIB installed in your org
- Understanding of Salesforce triggers and governor limits
- Custom Metadata configuration access
- Basic Apex development knowledge
The framework follows this pattern:
-
Trigger → calls
rflib_TriggerManager.dispatch() - TriggerManager → reads Custom Metadata configuration
- Configuration → defines which handlers to execute
- Handlers → contain your business logic
Create a new Apex class that implements the rflib_TriggerHandler interface:
public class AccountValidationHandler implements rflib_TriggerHandler {
private static final rflib_Logger LOGGER = rflib_LoggerUtil.getFactory().createLogger('AccountValidationHandler');
public void run(rflib_TriggerManager.Args args) {
LOGGER.info('AccountValidationHandler.run() invoked for {0} event with {1} records',
args.operation, args.newRecords.size());
if (args.operation == TriggerOperation.BEFORE_INSERT ||
args.operation == TriggerOperation.BEFORE_UPDATE) {
validateAccounts(args.newRecords);
}
}
public void onConsecutiveRun(rflib_TriggerManager.Args args, Integer numInvocation) {
LOGGER.warn('AccountValidationHandler recursive execution #{0} detected', numInvocation);
// Handle recursive scenario if needed
}
private void validateAccounts(List<Account> accounts) {
// Your validation logic here
for (Account acc : accounts) {
if (String.isBlank(acc.Phone)) {
acc.addError('Phone number is required');
}
}
}
}Create a single trigger for all events (recommended approach):
trigger AccountTrigger on Account (before insert, after insert, before update, after update, before delete, after delete) {
rflib_TriggerManager.dispatch(Account.SObjectType);
}Create a Trigger Configuration record:

Required Fields:
- Label: Descriptive name (e.g., "Account Validation Handler")
-
Object_Name__c:
Account -
Event__c:
BEFORE_INSERT(or your desired trigger event) -
Class_Name__c:
AccountValidationHandler -
Active__c:
true -
Order__c:
1(execution order if multiple handlers exist)
You can have multiple handlers for the same object and event:
Handler 1: Account Validation (Order: 1)
Handler 2: Account Enrichment (Order: 2)
Handler 3: Account Integration (Order: 3)
The framework automatically respects these feature switches:
-
rflib_Disable_All_Triggers: Disable switch
// This check is built into the framework
if (rflib_FeatureSwitch.isTurnedOff('rflib_Disable_All_Triggers')) {
LOGGER.warn('All triggers disabled via feature switch');
return;
}
Here's a test template for your trigger framework:
@IsTest
@SuppressWarnings('PMD.ClassNamingConventions')
private class AccountTriggerTest {
private static final String AFTER_INSERT = TriggerOperation.AFTER_INSERT.name();
@TestSetup
static void makeData(){
rflib_TestUtil.prepareLoggerForUnitTests();
}
@IsTest
public static void testTriggerExecution() {
// Mock the trigger configuration
rflib_TriggerManager.QUERY_LOCATOR = new rflib_MockTriggerConfigQueryLocator(
createConfiguration(),
Account.SObjectType.getDescribe().getName(),
AFTER_INSERT
);
Test.startTest();
insert new Account(Name = 'Test Company');
Test.stopTest();
// Verify the handler was invoked
System.assertEquals(1, rflib_MockTriggerHandler.CAPTURED_RUN_TRIGGER_ARGS.size());
rflib_TriggerManager.Args args = rflib_MockTriggerHandler.CAPTURED_RUN_TRIGGER_ARGS.get(0);
Account capturedRecord = (Account) args.newRecords.get(0);
System.assertEquals('Test Company', capturedRecord.Name);
}
private static rflib_Trigger_Configuration__mdt createConfiguration() {
return new rflib_Trigger_Configuration__mdt(
Active__c = true,
Class_Name__c = 'rflib_MockTriggerHandler',
Object_Name__c = 'Account',
Event__c = AFTER_INSERT,
Order__c = 1
);
}
}Key Points:
- Use
rflib_TestUtil.prepareLoggerForUnitTests()in test setup - Mock the configuration using
rflib_MockTriggerConfigQueryLocator - Verify handler execution with
rflib_MockTriggerHandler.CAPTURED_RUN_TRIGGER_ARGS
The Trigger Framework automatically supports these feature switches:
| Switch Name | Description | Use Case |
|---|---|---|
rflib_Disable_All_Triggers |
Disables all trigger execution | Emergency circuit breaker |
Turning off all triggers can be useful when dealing with large data operations using Dataloader or ETL tools.
Troubleshooting Checklist:
- ✅ Custom Metadata record exists and is Active
- ✅ Object_Name__c matches exactly (
Account, notaccount) - ✅ Event__c matches the trigger event (
BEFORE_INSERT) - ✅ Class_Name__c matches your handler class name exactly
- ✅ Handler class implements
rflib_TriggerHandlerinterface - ✅ No feature switches disabling triggers
Solution: The framework tracks recursion automatically. Implement logic in onConsecutiveRun():
public void onConsecutiveRun(rflib_TriggerManager.Args args, Integer numInvocation) {
if (numInvocation > 2) {
LOGGER.error('Too many recursive calls, skipping execution');
return;
}
// Handle controlled recursion
}Solution: Check the Order__c field in your configurations:
Handler A: Order = 1 (executes first)
Handler B: Order = 2 (executes second)
Handler C: Order = 3 (executes third)
✅ DO:
- Create one trigger per object with all events
- Use descriptive handler class names
- Implement proper logging in handlers
- Use the
Order__cfield for execution sequence - Test trigger with the provided test template
- Handle bulk operations (200 records)
❌ DON'T:
- Create multiple triggers per object
- Put business logic directly in triggers
- Forget to activate your Custom Metadata records
- Hard-code values in handlers
- Bulkification: Always handle collections, not single records
- SOQL Optimization: Use selective queries within handlers
- Governor Limits: Monitor DML and SOQL usage across all handlers
- Async Processing: Consider Retryable Actions for heavy operations
- Start Simple: Create one handler for one object and event
- Test Thoroughly: Use the provided test template
- Monitor Performance: Use the Ops Center to watch for issues
- Scale Gradually: Add more handlers as needed
Related Topics:
- Feature Switches - Control trigger execution
- Retryable Actions - Async processing from triggers
- Logging - Monitor trigger execution