Part 71: Useful Open Source Libraries for Salesforce
Welcome back to the Salesforce blog series. This is Part 71, and it marks the final post in Topic 3: The Complete Guide to Apex Development. Over the course of this topic, we have covered everything from basic syntax and data types all the way through advanced patterns like enterprise architecture, mocking frameworks, and asynchronous processing. Before we move on to Topic 4 (Lightning Web Components), there is one more critical area to explore: open source libraries.
The Salesforce ecosystem has a thriving open source community. Developers around the world have built and maintained libraries that solve common problems, reduce boilerplate, and bring battle-tested patterns into your org. Knowing which libraries exist, what they do, and how to use them can save you hundreds of hours of development time. In this post, we will cover the most important open source libraries available to Salesforce developers today, and wrap up with a hands-on project using one of them.
What Is an Open Source Library?
An open source library is a collection of code that is publicly available, typically hosted on a platform like GitHub, and licensed in a way that allows anyone to use, modify, and distribute it. In the Salesforce world, open source libraries are usually distributed as unlocked packages or unmanaged packages that you can install directly into your org or scratch org.
There are several reasons why open source libraries matter for Salesforce development:
- They solve common problems. Instead of writing your own logging framework or rollup engine from scratch, you can leverage something that has already been built, tested, and refined by the community.
- They encourage best practices. Many of these libraries are maintained by well-known Salesforce architects and MVPs who bake solid design patterns directly into their code.
- They reduce technical debt. A well-maintained open source library receives bug fixes, security patches, and feature updates over time. Your custom-built equivalent likely does not get the same level of ongoing attention.
- They are free. While some AppExchange products charge licensing fees, open source libraries are available at no cost.
That said, adopting an open source library is not without risk. You need to consider whether the library is actively maintained, whether its license is compatible with your organization’s policies, and whether you have the expertise to troubleshoot issues if they arise. Always review the library’s GitHub repository for recent commit activity, open issues, and documentation quality before committing to it.
The Apex Common and Apex Mocks Libraries
If you have been following this series, you already encountered Apex Common (also known as fflib-apex-common) back in Part 64 when we discussed enterprise application architecture and the separation of concerns pattern. Apex Common provides the foundational classes for implementing the Service Layer, Domain Layer, Unit of Work, and Selector Layer patterns in your Salesforce org.
What Apex Common Provides
At its core, Apex Common gives you:
- fflib_SObjectDomain — A base class for your domain layer, providing trigger handling, validation, and field defaulting hooks.
- fflib_SObjectUnitOfWork — An implementation of the Unit of Work pattern that manages DML operations and ensures they happen in the correct order.
- fflib_SObjectSelector — A base class for building query classes that enforce field-level security and produce consistent, testable SOQL.
- Application class factory — A registry that maps SObject types to their corresponding domain, selector, and service classes, making it easy to swap implementations for testing.
Apex Mocks
Apex Mocks (fflib-apex-mocks) is the companion library to Apex Common. As we covered in Part 66, Apex Mocks brings the power of the Mockito-style mocking framework to Apex. It allows you to create mock implementations of your selectors, domains, and services so that your unit tests can run without hitting the database.
Here is a quick reminder of what a test looks like using Apex Mocks:
@IsTest
static void testServiceWithMockedSelector() {
// Create mocks
fflib_ApexMocks mocks = new fflib_ApexMocks();
IAccountsSelector mockSelector = (IAccountsSelector) mocks.mock(AccountsSelector.class);
// Stub the selector
mocks.startStubbing();
mocks.when(mockSelector.sObjectType()).thenReturn(Account.SObjectType);
mocks.when(mockSelector.selectById(new Set<Id>{ testAccountId }))
.thenReturn(new List<Account>{
new Account(Id = testAccountId, Name = 'Test Account')
});
mocks.stopStubbing();
// Inject the mock
Application.Selector.setMock(mockSelector);
// Execute and assert
Test.startTest();
AccountService.processAccounts(new Set<Id>{ testAccountId });
Test.stopTest();
// Verify the selector was called
((IAccountsSelector) mocks.verify(mockSelector))
.selectById(new Set<Id>{ testAccountId });
}
Installation
Both libraries are available as unlocked packages. You can install them using the Salesforce CLI:
# Install Apex Common
sf package install --package 04t6S000001UhOEQA0 -w 10
# Install Apex Mocks
sf package install --package 04t6S000001UhOJQA0 -w 10
Check the GitHub repositories for the latest package version IDs, as they are updated regularly.
Universal Mocker
While Apex Mocks is powerful, it comes with a learning curve and requires you to structure your code around the fflib patterns. Universal Mocker, created by James Simone, offers a lighter-weight alternative for developers who want mocking capabilities without adopting the full fflib stack.
How Universal Mocker Works
Universal Mocker uses the System.StubProvider interface introduced by Salesforce to create mock objects dynamically. The key advantage is that it works with any Apex interface — you do not need to use fflib’s application factory or follow its specific patterns.
Here is a basic example:
@IsTest
static void testWithUniversalMocker() {
UniversalMocker mockInstance = UniversalMocker.mock(IAccountRepository.class);
// Set up the mock to return specific data
mockInstance.when('getAccountsByIds')
.thenReturn(new List<Account>{
new Account(Name = 'Mocked Account')
});
IAccountRepository mockedRepo = (IAccountRepository) mockInstance.createStub();
// Inject and use the mock
AccountService service = new AccountService(mockedRepo);
List<Account> results = service.fetchAccounts(new Set<Id>{ fakeId });
System.assertEquals(1, results.size());
System.assertEquals('Mocked Account', results[0].Name);
// Verify call count
mockInstance.assertThat().method('getAccountsByIds').wasCalled(1);
}
When to Use Universal Mocker vs. Apex Mocks
- Use Apex Mocks when you are already invested in the fflib/Apex Common architecture and want tight integration with its Application factory.
- Use Universal Mocker when you want a simpler, more flexible mocking solution that works with any interface-based code, regardless of the underlying architecture.
Universal Mocker is available on GitHub and can be deployed to your org as source or via an unlocked package.
Nebula Logger
Nebula Logger, created and maintained by Jonathan Gillespie, is arguably the most popular open source library in the Salesforce ecosystem. It provides a robust, feature-rich logging framework that far exceeds what you can accomplish with System.debug() alone.
Why You Need a Logging Framework
If you have ever tried to debug a complex issue in a production org, you know the pain. System.debug() statements are only visible in debug logs, which have size limits, retention limits, and are difficult to search. You cannot easily correlate logs across transactions, and you certainly cannot build dashboards or alerts on top of them.
Nebula Logger solves all of these problems by writing log entries to custom objects in your org, making them queryable, reportable, and retainable for as long as you need.
Key Features
- Multiple logging levels — FINEST, FINER, FINE, DEBUG, INFO, WARN, ERROR. You control which levels are captured through custom metadata configuration.
- Automatic context capture — Each log entry automatically records the class name, method name, line number, stack trace, running user, transaction ID, and more.
- Cross-transaction correlation — Nebula Logger can link logs across parent and child transactions (for example, a Platform Event triggered by a Queueable).
- Flow and Process Builder support — You can log directly from Flow using invocable actions, not just from Apex.
- Tagging and scenarios — You can tag log entries and group them by scenario for easier searching and filtering.
- Plugin architecture — Nebula Logger supports plugins that let you extend its behavior. For example, you can send log entries to Slack, an external monitoring service, or a big object for long-term storage.
Basic Usage
public class AccountService {
public static void processAccounts(List<Account> accounts) {
Logger.info('Starting account processing', accounts);
try {
for (Account acc : accounts) {
if (String.isBlank(acc.Name)) {
Logger.warn('Account has blank name', acc);
continue;
}
// Process the account
Logger.debug('Processing account: ' + acc.Name);
}
update accounts;
Logger.info('Successfully processed ' + accounts.size() + ' accounts');
} catch (Exception ex) {
Logger.error('Failed to process accounts', ex);
} finally {
Logger.saveLog();
}
}
}
Installation
Nebula Logger is distributed as an unlocked package:
sf package install --package 04t5Y0000015lhiQAA -w 10
As always, check the repository for the latest version. After installation, you will find new custom objects (Log__c, LogEntry__c, LoggerTag__c) and a suite of custom metadata types for configuration. Nebula Logger also ships with a dedicated Lightning app for browsing and managing your logs.
Apex Rollup
Apex Rollup, also created by James Simone, is an open source alternative to Declarative Lookup Rollup Summaries (DLRS). It allows you to create rollup summary calculations between any two objects that have a lookup relationship — something Salesforce natively restricts to master-detail relationships.
Why Apex Rollup?
Salesforce’s native rollup summary fields only work on master-detail relationships, and they have limitations on the number of rollup fields per object, the types of calculations supported, and the filter criteria you can apply. DLRS was the long-standing community solution, but it relies on the older SOQL-based approach and can be difficult to configure at scale.
Apex Rollup takes a modern approach:
- Works with any lookup relationship — not just master-detail.
- Supports a wide range of operations — SUM, COUNT, COUNT_DISTINCT, AVERAGE, MIN, MAX, FIRST, LAST, CONCAT, CONCAT_DISTINCT, and more.
- Configurable via custom metadata, Flow, or Apex — choose the approach that fits your team.
- Handles large data volumes — uses queueable chaining and platform events for async processing.
- Full recalculation support — you can trigger a full recalc of all rollup values when needed.
Configuration via Custom Metadata
The easiest way to set up a rollup is through the Rollup__mdt custom metadata type that ships with the package. Here is an example configuration:
| Field | Value |
|---|---|
| Rollup Operation | SUM |
| Calc Item (Child Object) | OpportunityLineItem |
| Rollup Field On Calc Item | TotalPrice |
| Lookup Field On Calc Item | OpportunityId |
| Rollup Object (Parent) | Opportunity |
| Rollup Field On Rollup Object | Custom_Total__c |
| Calc Item Where Clause | Product2.IsActive = true |
With this metadata record in place, any time an OpportunityLineItem is created, updated, or deleted, Apex Rollup will automatically recalculate the Custom_Total__c field on the parent Opportunity.
Invocation from Apex
You can also invoke rollups directly from Apex code:
Rollup.performRollup(new List<Rollup.FlowInput>{
new Rollup.FlowInput()
.rollupOperation('SUM')
.calcItemField('Amount')
.lookupFieldOnCalcItem('AccountId')
.rollupFieldOnLookupObject('Total_Opportunity_Amount__c')
.lookupSObjectName('Account')
.calcItemRecords(Trigger.new)
});
We will use Apex Rollup in the hands-on project at the end of this post.
Open Source Trigger Frameworks
Back in Part 44, we discussed trigger best practices and the importance of having a trigger framework. The Salesforce community has produced several open source trigger frameworks, each with different design philosophies.
Popular Options
Kevin O’Hara’s Trigger Handler
One of the most widely adopted trigger frameworks due to its simplicity. You extend a single TriggerHandler base class and override the methods you need:
public class AccountTriggerHandler extends TriggerHandler {
protected override void beforeInsert() {
// Handle before insert logic
for (Account acc : (List<Account>) Trigger.new) {
if (String.isBlank(acc.BillingCountry)) {
acc.BillingCountry = 'United States';
}
}
}
protected override void afterUpdate() {
// Handle after update logic
AccountService.processUpdatedAccounts(
(List<Account>) Trigger.new,
(Map<Id, Account>) Trigger.oldMap
);
}
}
Your trigger itself is a single line:
trigger AccountTrigger on Account (before insert, before update, after insert, after update, before delete, after delete, after undelete) {
new AccountTriggerHandler().run();
}
This framework also provides methods for bypassing triggers and controlling recursion.
fflib_SObjectDomain (Apex Common)
If you are using Apex Common, you already have a trigger framework built in. The domain layer classes serve as your trigger handlers, and the framework routes trigger events to the appropriate methods (onBeforeInsert, onAfterUpdate, etc.) automatically.
Trigger Actions Framework (by Mitch Spano)
A more recent addition to the ecosystem, this framework takes a metadata-driven approach. Instead of hardcoding which handler classes run for which trigger events, you configure them via custom metadata records. This makes it possible for administrators to reorder, enable, or disable individual trigger actions without deploying code.
// Custom metadata record:
// SObject: Account
// Event: Before Insert
// Apex Class: SetDefaultBillingCountry
// Order: 1
// Active: true
The advantage of this approach is flexibility and governance. In large orgs with many teams, being able to manage trigger execution order through metadata rather than code deployments can be a significant operational improvement.
Which Framework Should You Choose?
- Kevin O’Hara’s Trigger Handler is best for small-to-medium orgs that want simplicity and minimal overhead.
- fflib_SObjectDomain is the natural choice if you are already using the fflib/Apex Common enterprise patterns.
- Trigger Actions Framework is ideal for large orgs with multiple development teams where governance and configurability are priorities.
There is no universally correct answer. Evaluate each framework against your team’s size, your org’s complexity, and your existing architecture.
PROJECT: Use Apex Rollup to Calculate Rollups on a Salesforce Object
Let us put one of these libraries into practice. In this project, we will install Apex Rollup and configure it to calculate rollup summaries on the Account object based on its related Opportunities.
Scenario
You want to track the following values on each Account:
- Total_Open_Opportunity_Amount__c — The sum of Amount for all Opportunities in an open stage.
- Open_Opportunity_Count__c — The count of Opportunities that are not Closed Won or Closed Lost.
These are fields that Salesforce’s native rollup summary cannot handle on Account because the Account-Opportunity relationship is not master-detail (it is a standard lookup).
Step 1: Install Apex Rollup
First, install the latest version of Apex Rollup into your scratch org or sandbox:
sf package install --package 04t6S000001Ni1eQAC -w 10
Verify the installation by checking Setup > Installed Packages. You should see the Apex Rollup package listed.
Step 2: Create Custom Fields on Account
Create two custom fields on the Account object:
Total_Open_Opportunity_Amount__c -- Currency(16, 2)
Open_Opportunity_Count__c -- Number(18, 0)
You can create these through Setup or by deploying metadata:
<!-- force-app/main/default/objects/Account/fields/Total_Open_Opportunity_Amount__c.field-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Total_Open_Opportunity_Amount__c</fullName>
<label>Total Open Opportunity Amount</label>
<type>Currency</type>
<precision>16</precision>
<scale>2</scale>
<required>false</required>
</CustomField>
<!-- force-app/main/default/objects/Account/fields/Open_Opportunity_Count__c.field-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Open_Opportunity_Count__c</fullName>
<label>Open Opportunity Count</label>
<type>Number</type>
<precision>18</precision>
<scale>0</scale>
<required>false</required>
</CustomField>
Step 3: Configure Rollup Metadata Records
Navigate to Setup > Custom Metadata Types > Rollup > Manage Records. Create two records:
Record 1: Total Open Opportunity Amount
| Field | Value |
|---|---|
| Label | Account Total Open Opp Amount |
| Rollup Operation | SUM |
| Calc Item SObject | Opportunity |
| Rollup Field On Calc Item | Amount |
| Lookup Field On Calc Item | AccountId |
| Rollup SObject | Account |
| Rollup Field On Rollup Object | Total_Open_Opportunity_Amount__c |
| Calc Item Where Clause | IsClosed = false |
| Is Rollup Started From Parent | false |
Record 2: Open Opportunity Count
| Field | Value |
|---|---|
| Label | Account Open Opp Count |
| Rollup Operation | COUNT |
| Calc Item SObject | Opportunity |
| Rollup Field On Calc Item | Id |
| Lookup Field On Calc Item | AccountId |
| Rollup SObject | Account |
| Rollup Field On Rollup Object | Open_Opportunity_Count__c |
| Calc Item Where Clause | IsClosed = false |
| Is Rollup Started From Parent | false |
Step 4: Deploy the Trigger
Apex Rollup needs a trigger on the child object to fire its calculations. If you installed the package, it may include an invocable action approach, but the most reliable method is a trigger. Create a trigger on Opportunity:
trigger OpportunityRollupTrigger on Opportunity (
after insert, after update, after delete, after undelete
) {
Rollup.runFromTrigger();
}
This single line tells Apex Rollup to evaluate all configured rollup metadata records for the Opportunity object whenever a trigger event fires.
Step 5: Test the Configuration
Create a test class to verify everything works:
@IsTest
static void testOpenOpportunityRollup() {
Account testAccount = new Account(Name = 'Rollup Test Account');
insert testAccount;
List<Opportunity> opps = new List<Opportunity>{
new Opportunity(
Name = 'Open Opp 1',
AccountId = testAccount.Id,
Amount = 5000,
StageName = 'Prospecting',
CloseDate = Date.today().addDays(30)
),
new Opportunity(
Name = 'Open Opp 2',
AccountId = testAccount.Id,
Amount = 10000,
StageName = 'Qualification',
CloseDate = Date.today().addDays(60)
),
new Opportunity(
Name = 'Closed Opp',
AccountId = testAccount.Id,
Amount = 7500,
StageName = 'Closed Won',
CloseDate = Date.today()
)
};
insert opps;
Test.startTest();
// Apex Rollup processes asynchronously, so we need to
// allow async work to complete
Test.stopTest();
Account result = [
SELECT Total_Open_Opportunity_Amount__c, Open_Opportunity_Count__c
FROM Account
WHERE Id = :testAccount.Id
];
System.assertEquals(15000, result.Total_Open_Opportunity_Amount__c,
'Sum of open opportunity amounts should be 15000');
System.assertEquals(2, result.Open_Opportunity_Count__c,
'Count of open opportunities should be 2');
}
Step 6: Perform a Full Recalculation (Optional)
If you are deploying Apex Rollup into an org that already has existing data, you will need to run a full recalculation to populate the rollup fields for all existing records. You can do this from Apex:
Rollup.performFullRecalculation(
'Opportunity', // Calc item SObject
'Amount', // Calc item field
'AccountId', // Lookup field
'Account', // Rollup SObject
'Total_Open_Opportunity_Amount__c', // Rollup field
'SUM', // Operation
'IsClosed = false' // Where clause
);
Run the equivalent command for the COUNT rollup as well. These recalculations run asynchronously using batch Apex under the hood, so they handle large data volumes gracefully.
Section Notes
- Open source libraries are publicly available code packages that solve common Salesforce development problems. Always evaluate maintenance activity, documentation, and license compatibility before adopting one.
- Apex Common (fflib-apex-common) provides the building blocks for enterprise application architecture in Salesforce: Domain Layer, Selector Layer, Service Layer, and Unit of Work.
- Apex Mocks (fflib-apex-mocks) is the companion mocking framework that enables true unit testing without database interaction when used with Apex Common.
- Universal Mocker is a lighter-weight mocking alternative that works with any interface-based Apex code, independent of the fflib patterns.
- Nebula Logger replaces
System.debug()with a full-featured logging framework backed by custom objects, supporting multiple log levels, automatic context capture, Flow integration, tagging, and plugin extensibility. - Apex Rollup brings rollup summary functionality to any lookup relationship, supporting a wide range of operations (SUM, COUNT, AVERAGE, CONCAT, and more) configured through custom metadata, Flow, or Apex code.
- Open source trigger frameworks range from simple (Kevin O’Hara’s TriggerHandler) to metadata-driven (Trigger Actions Framework). Choose based on your org’s complexity and governance requirements.
- When adopting any open source library, check the GitHub repository for recent commits, open issues, contributor count, and documentation quality.
- This concludes Topic 3: The Complete Guide to Apex Development. In the next post, we begin Topic 4: Lightning Web Components.