A managed package is a collection of application components posted as a unit on AppExchange and associated with a namespace and a License Management Organization.
Salesforce has two ways that you can build managed packages, first-generation packaging (1GP) and second-generation packaging (2GP).
A Salesforce application from a managed package is typically static – meaning its functionality can’t be changed after installation.
However, customers usually have differing requirements, so it’s often advantageous to be able to change the application for specific customers.
In fact:
An application setting is a value that controls how a feature behaves within the application. One or more settings can be used to manage the application.
For example, the validation rules can all use a master switch to turn an application’s setting on or off by having them all reference an ‘active’ checkbox on a custom setting or metadata record.
This is usually used during data imports after an initial installation. The installation ensures customers’ data can be imported by bypassing the application’s validation.
The threshold can be stored in the managed package’s metadata as a number and used via automation to maintain each account’s status.
Who can control the application settings is also essential. Through using protected, public custom settings, or metadata records, the managed package developer controls who can adjust the settings.
Protected custom settings or metadata records can only be updated by the managed package developer – not the customer. The developer does this by logging into the customer’s org using the Licensing Management Application (LMA).
Dependency Injection and Inversion of Control is a coding pattern that lets a person represent the code required at runtime. This allows the user to create different codes outside of the package in a customer’s org to change the behavior in the managed package.
The class can be read from your application setting. Your setting allows the managed package’s Apex application code to be changed as per customers’ requirements. This change is achieved through implementing a new class in the customer’s org and updating the application setting to specify the new class.
For example, an independent software vendor (ISV) has an order management module in their product that lets their customers offer products for sale. Salesforce has a standard set of product pricing in the app, but many customers often have unique pricing requirements.
From a coding standpoint, their managed package uses Dependency Injection with the Apex “Type.ForName” and “Type.newInstance” methods.
Let’s see how a customer’s pricing requirements come to light through the necessary coding.
global class ProductInfo {
global Id ProductId { get; set; }
global Decimal Quantity { get; set; }
}
global class ProductPricingRequest {
global List<ProductInfo> ProductsToPrice { get; set; }
}
global class ProductPricing {
global ProductInfo ProductInfo { get; set; }
global Decimal UnitPrice { get; set; }
}
global class ProductPricingResponse {
global List<ProductPricing> ProductPricings { get; set; }
}
global interface IProductPricer {
ProductPricingResponse priceProducts(ProductPricingRequest pricingRequest);
}
global class StandardProductPricer implements IProductPricer {
global ProductPricingResponse priceProducts(ProductPricingRequest pricingRequest) {
ProductPricingResponse response = new ProductPricingResponse();
// Standard Pricing Logic Here
return response;
}
}
public class OrderService {
public ProductPricingResponse priceProducts(List<ProductInfo> productsToPrice) {
// The name of the product pricer class to use is stored in an application setting
// somewhere that's configurable. Its value is used here.
String productPricerClassName = 'StandardProductPricer';
Type productPricerType = Type.forName(productPricerClassName);
IProductPricer productPricer = (IProductPricer) productPricerType.newInstance();
ProductPricingRequest pricingRequest = new ProductPricingRequest();
pricingRequest.ProductsToPrice = productsToPrice;
return productPricer.priceProducts(pricingRequest);
}
}
Custom user interfaces are often needed for functionality not provided by Salesforce. These interfaces are configurable. Configuration can be done through your application settings stored in the managed package’s metadata or through the design settings of Lightning Components.
For example, a custom form has multiple fields that often change per customer. The managed package uses protected custom metadata records to specify the fields to display.
If a customer’s code uses a global method and its signature changes, the customer’s code will not compile (which can cause errors).
Using a request object and response object, a user can add adjusted properties and variables as needed for further input and output without changing the function’s signature.
Let’s say your managed package has an ordering module, and the pricing for each product differs. This means that the pricing logic has to be flexible enough to accept new input and provide created output. The coding for this functionality will look like this:
global class ProductPricer {
global PricingResponse getPricing(ProductPricingRequest request) {
// determine product pricing using info provided in request and return info
// in Pricing Response
}
}
global class ProductPricingRequest {
global List<ProductDetail> Products = new List<ProductDetail>()
}
global class ProductDetail {
global String ProductId = ‘’;
global Integer Quantity = 0;
global Map<String, Object> CustomProperties = new Map<String, Object>();
}
global class PricingResponse {
global List<ProductPrice> Prices = new List<ProductPrice>();
}
global class ProductPrice {
global String ProductId = ‘’;
global Decimal Price = 0;
global Map<String, Object> CustomProperties = new Map<String, Object>();
}
Using a publish and subscribe pattern allows an organization’s application to post events. One or more subscribers can perform the necessary actions using each event’s information. This will enable customers to subscribe to the managed package’s published events and perform customer-specific behavior as needed.
For example, the managed package has an ordering module, and when an order is completed, it publishes a completed order event with a customer receipt.
Let’s say a particular customer has a fulfillment system to integrate. They can now subscribe to these completed order events and listen for new orders. This information can be sent to the fulfillment system to process the order.
A platform event-triggered Flow, for example, can be used to send a customer their receipt. The customer’s IT department can use the new Salesforce publication and subscription application programming interface (API) to integrate with the fulfillment system.
Another example is inter-component communication between Lightning Components in a managed package. Often, multiple custom Lightning Components will interact with each other on a Lightning page.
These events provide a customer-specific functionality when the package’s component or components publish that event.
Let’s take a closer look at how you can use a platform event so that when an order is completed, the customer is emailed a receipt.
First, a platform event is created with these definitions:
The custom fields involved:
public class CustomerReceiptService {
public void sendReceipts(Set<String> orderIds) {
// Code to send the email receipts using the email system's API(s).
}
}
trigger CompletedOrderTrigger on Completed_Order__e (after insert) {
Set<String> completedOrderIds = new Set<String>();
for (Completed_Order__e completedOrder : Trigger.New) {
completedOrderIds.add(completedOrder.Order_Id__c);
}
CustomerReceiptService crs = new CustomerReceiptService();
crs.sendReceipts(completedOrderIds);
}
You can create anything for your organization if you follow the necessary managed package steps and parameters.
You can customize your products, change pricing, and even provide accurate and up-to-date receipts.