Managed Package Extensibility Ideas

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.

Managed Package Extensibility Ideas
Table of contents

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:

So, let’s explore different options to make a managed package more extensible and customizable to help fulfill customers’ personalized needs.

Extensibility Options

Application Settings

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.

Another example is with a threshold. Often, customers want to distinguish their large accounts from their regular ones, so they determine some sales threshold that – when it is met or exceeded – it marks the customer as a ‘significant’ one.

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

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.

In Apex, this coding pattern is made using the “Type.ForName” and “Type.newInstance” methods. These methods let you represent the specified class at runtime. 

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.

An Order Price Code Example

Let’s see how a customer’s pricing requirements come to light through the necessary coding.

  • First, data structure classes are needed. A product information class is designed to store the information for a single product representing all the details required to price it.
global class ProductInfo {
    global Id ProductId { get; set; }
    global Decimal Quantity { get; set; }
  • Each product has a “ProductId” to query its information from the database. The class also has a decimal for the quantity so that the pricing logic knows how many of these products are being ordered.
  • Discounts based on bulk pricing are global so anyone can use this class outside the managed package. A class is also used to add additional properties for future package releases.
  • A “ProductPricingRequest” class is created as a parameter object to the product pricing function. It’s a container holding the list of product information. You can add more input in a future version if needed.
global class ProductPricingRequest {
    global List<ProductInfo> ProductsToPrice { get; set; }
  • The product pricing class represents the pricing information for a single product and contains the “ProductInfo” instance used and its unit price.
global class ProductPricing {

    global ProductInfo ProductInfo { get; set; }
    global Decimal UnitPrice { get; set; }
  • Next, a product pricing response class is created containing a list of “ProductPricing.” This data will be returned from the product pricing function.
global class ProductPricingResponse {
    global List<ProductPricing> ProductPricings { get; set; }
  • Now that the data structures are created, let’s make the product price interface named “IProductPricer.”
global interface IProductPricer {

    ProductPricingResponse priceProducts(ProductPricingRequest pricingRequest);
  • The interface has a single function that takes in a “ProductPricingRequest” with the list of products to price. This structure returns a “ProductPricingResponse” so that all of the products and their prices can be returned to the requestor.
  • Using parameter and response objects allows an ISV to add properties to each product. These properties create further input and output in future versions without changing the price product’s function signature.
  • The standard product price will then need to be developed, which the managed package will use.
global class StandardProductPricer implements IProductPricer {

    global ProductPricingResponse priceProducts(ProductPricingRequest pricingRequest) {
        ProductPricingResponse response = new ProductPricingResponse();
        // Standard Pricing Logic Here

        return response;
  • The “StandardProductPricer” class implements the “IProductPricer” interface and its “priceProducts” function. This type of class implements the logic needed to price the requested products. Your coding will scan the Salesforce database to get the needed product information, pricing data for each product, and any other information required. It then returns the priced products in the response.
  • Finally, create an “OrderService” class that the front-end order management module invokes to get the pricing information.
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);

Configurable User Interfaces

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.
Another example is an Aura or Lightning Web Component that uses design time settings specified in the Lightning App Builder or Experience Cloud Builder to control its behavior. One setting could control how many records to show, while another setting could control the record type ID for a specific record.

Request And Response Objects For Global Apex Methods Or Functions

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.

An Apex Code Example

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>();
  • The “ProductPricer” class has the “getPricing” function to determine the pricing information for a list of products.
  • The “ProductPricer” class has the “getPricing” function to determine the pricing information for a list of products.
  • Another input is “ProductPricingRequest,” an object parameter used as an input to the “getPricing” function.
  • The function currently accepts a list of products where each one has a product ID and quantity. Each product also has a “CustomProperties” map, so you can provide additional details for custom pricing that may be done in a customer’s org.
  • Your “getPricing” function will use the “PricingResponse” object parameter to return a list of prices where each “ProductPrice” has a product ID and its cost. The “ProductPrice” also has the “CustomProperties” map, so you can pass back extra pricing information from custom pricing logic if needed. Map collections can be used as a generic container for custom pricing logic to pass pricing input and back pricing output per product.
  • These request and response classes can be extended by adding global properties to accept input and provide output in newer managed package releases.

Publish And Subscribe

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.
They can communicate by publishing and subscribing to events in JavaScript. An implementation team can create customer-specific Lightning Components that subscribe to the published managed package. 

These events provide a customer-specific functionality when the package’s component or components publish that event.

Order Notification Examples

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:

  • Label: Completed order
  • Plural label: Completed orders
  • Description: When an order is completed, this event is published with the order ID
  • Object name: “Completed_Order.”

The custom fields involved:

Field Name

API Name

Date Type

Order ID



  • The event has an order ID, so a subscriber can query the information as needed from the Salesforce database. Extra attributes can be added as required.
  • The event has an order ID, so a subscriber can query the information as needed from the Salesforce database. Extra attributes can be added as required.
  • An ISV sells many orders on any given day. Salesforce’s limits make it impractical to email customer receipts. This is why the software will use a third-party emailing system’s API.
  • Next, a completed order platform event Apex trigger will need to be created, subscribing to new completed order events and sending each customer their receipt.
  • A service class is used to call out the email system’s API.
public class CustomerReceiptService {

    public void sendReceipts(Set<String> orderIds) {
        // Code to send the email receipts using the email system's API(s).
  • The completed order platform event Apex trigger will need to be created as it invokes that service to send the email receipts.
trigger CompletedOrderTrigger on Completed_Order__e (after insert) {
    Set<String> completedOrderIds = new Set<String>();
    for (Completed_Order__e completedOrder : Trigger.New) {
    CustomerReceiptService crs = new CustomerReceiptService();
  • Your order management code publishes the completed order event, and the subscribers can then use it to perform the actions needed.
  • Order management logic can then be separated from all the functionality when an order is completed. This type of logic allows completed order functionality to be implemented by adding more subscribers.

Get Ready To Extend Your Managed Packages

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.

Extensibility is limitless, so treat your managed packages as if the sky’s the limit.