Salesforce CPQ – Recommended Products Plugin

This post is part of an impromptu series on Salesforce CPQ tools. The other posts in the series are provided below, as well as some links to related blog posts that have come up along the way!

Blog Posts from Series:

  1. Guided Selling Basics
  2. Product Configuration Initializer
  3. Product Search Executor
  4. Product Search Plugin
  5. Product Search Plugin – Extended Example
  6. Recommended Products Plugin (this post)

** Note that I intentionally did not cover the Quote Calculator Plugin (QCP) as this one is in wide use with a lot of resources available out there.

Blog Posts on Related Topics:

  1. Chris HickmanPre-fill Guided Selling Inputs
  2. Tejas KapasiPreventing Duplicate Selections using QCP

Salesforce Documentation for Recommended Products:

  1. Recommended Products – Release Notes
  2. Recommended Products – Developer Guide

The Background

In the Winter ’21 release, Salesforce CPQ tool added the Recommended Products Plug-in.  This allows us the ability to provide a list of recommended products based on the selections made so far in the Quote.  This is an additional way by which we can guide Sales teams towards related products, upsell/upgrade opportunities, promotional items, and similar recommendations.  The developer documentation is strong for this feature, so we will just focus on applying it to a ‘real-life’ example to help visualize how it works.

Scenario Review

In this scenario, we’ll assume that we’ve added our popular Gaming Laptop bundle as a Quote Line on our Quote.  Based on that selection, we’ll add the ability for the Sales team to view and add Recommended Products related to that Laptop – either items we frequently sell together, or perhaps ones we want to encourage sales for based on Promotions, etc.  In our use case, we’ll provide a list of a few products that we want to promote for add-on purchase. 

Note that we could also apply a very similar approach to that taken in the Product Search Plugin, where we Recommend Products only as long as they haven’t already been added to the Quote. I added details under Notes & Considerations section to cover this small adjustment.

Master Data Setup

As per the documentation, we need to create and maintain a custom object where we will store our recommended products.  For our example, we’ll keep this simple and call the object ‘ProductRecommendation__c’ in order to match the documentation and save some confusion.

On the new object, we will add a few fields to allow us to find the appropriate Products to recommend.  There is a minimum of 2 required – both lookups to Product.  I’ve called mine Product__c (what Product on the Quote Line is driving the Recommendation) and Recommendation__c (the Product being Recommended). 

I’ve also added a ‘Region__c’ picklist field to match against a custom ‘Quote_Region__c’ field on the Quote object– just as a basic idea of how we might drive some branching functionality based on Quote attributes.  If you want to fully follow along, create a picklist on your SBQQ__Quote__c object called ‘Quote_Region__c’ and add picklist values ‘NA’ and ‘EMEIA’.  There are tons of potential custom fields we could use here to filter on other Quote or Quote Line attributes, depending on the specific business scenario. 

**Note that I did change the fields around a bit vs. what is in the developer docs, so be careful there to match what you did.

Once we’ve set up the object, let’s load a few example records that we can use to test out our Plugin.  Since we already have some of the options set up previously, we can go ahead and use those to avoid creating more Product records.  Below I’ve set up Gaming Headset, Gaming Mousepad and Monitor as Recommendations for the Laptop.  I have Region set to NA for Gaming Headset and Gaming Mousepad, and EMEIA for Monitor.  This would mean that we want to suggest the first 2 if the Quote is for Region = NA and the Monitor if the Quote is for Region = EMEIA.  Again, this isn’t a realistic scenario but is just to illustrate that we can branch on custom attributes.

Custom Action Setup

The ‘Add Recommendations’ Custom Action was added via CPQ package upgrade, so it will be available in your org by default, but set to Active = false.  Locate it and flip to Active = true, and adjust the Parent Custom Action or other attributes as necessary

Plug-In Setup

Now we will write the class to implement the SBQQ.ProductRecommendationPlugin interface provided by the package.  Always on the lookout for original nomenclature, we will use ‘TestProductRecommendationPlugin’ for our name. 

One ‘gotcha’ here is that the documentation refers to the interface as ‘ProductRecommendationPlugin’. 

Because it belongs to the SBQQ namespace, we need to preface our implementation reference to the interface as ‘SBQQ.ProductRecommendationPlugin’ as you’ll see in the code snippet below.

For our implementation, I’ve left everything just about the same aside from changing the api names around to match my fields.  Additionally, I’ve added the logic to pull the Quote_Region__c field from the Quote and filter the Product Recommendations against it.

global class TestProductRecommendationPlugin implements SBQQ.ProductRecommendationPlugin {
    global PricebookEntry[] recommend(SObject quote, List<SObject> quoteLines) {
        // Get the price book Id of the quote
        Id pricebookId = (Id)quote.get('SBQQ__PriceBookId__c');
        String region = (String)quote.get('Quote_Region__c');
        // Get Ids of all products in the quote
        Id[] productIdsInQuote = new Id[0];
        for (SObject quoteLine : quoteLines) {
            Id productId = (Id)quoteLine.get('SBQQ__Product__c');
       // Query the recommendation custom object records of all products in quote.
       ProductRecommendation__c[] recommendations = [
            SELECT Recommendation__c
            FROM ProductRecommendation__c
            WHERE Product__c IN :productIdsInQuote AND Region__c = :region
        // Get Ids of all recommended products
        Id[] recommendedProductIds = new Id[0];
        for(ProductRecommendation__c recommendation : recommendations) {
        // Query the price book entries of the above recommended products
        PricebookEntry[] priceBookEntries = [
            SELECT Id, UnitPrice, Pricebook2Id, Product2Id, Product2.Name, Product2.ProductCode
            FROM PricebookEntry
            WHERE Product2Id IN :recommendedProductIds AND Pricebook2Id = :pricebookId];
        return priceBookEntries;

Save the Class once done and let’s move along.

Update CPQ Package Configuration

Once the Product Recommendation Plugin is saved, we need to add it to the CPQ package configuration.  Navigate there, locate the ‘Recommended Products Plugin’ text box, and copy/paste in the ‘TestProductRecommendationPlugin’ value as shown.  Save and we’re good to go.

Testing it Out

The first thing we’ll check is that the ‘Add Recommendations’ button shows under our ‘Add Products’ button in the QLE.  Check!

Next we’ll check that with an empty quote we don’t have any recommendations to provide.  Check

Note this is based on how we’ve set up for this example, but perhaps there are indeed some Products you always want to show.  Just to test that out I’ve temporarily added a Product Recommendation for when there are no Quote Lines and added an OR to the SOQL to bring this recommendation.  I’m not sure that’s the best idea, but thought to check it so sharing it just in case!

After this adjustment, when trying the recommendations we see the Gaming Laptop.  Check!  Reverting the above scenario now ????

Back to our main example, let’s now add the Gaming Laptop using Guided Selling as per our previous blog posts.  Use Quick Save to commit the Quote Lines, so that we can test the Recommendations

Now for the real test – click on ‘Add Recommendations’ now that we have the Quote Line, and let’s verify that the 2 Recommended Products appear, as this Quote has a Region of ‘NA’.  Check

Lastly,  I’ll change my Quote region to ‘EMEIA’ and verify that only the Monitor shows up.  Check!

So everything is working just as we’d expect, and we’re suggesting products regionally, based on previously added Quote Lines.

Developer Documentation – Items to Note

The following notes are provided in the developer documentation, and thus important to keep in mind:

  1. A maximum of 2,000 price book entries are shown on the Recommended Products page, similar to the limit on the Product Lookup page. Your implementation class can return up to your top 2,000 recommendations. If you have more, you can use a recommendation score to sort and choose your top 2,000 products.
  2. The Recommended Products plugin doesn’t support the Large Quote Threshold setting.
  3. Salesforce CPQ prioritizes field-level security over Recommended Product plugins. If your plugin includes fields that your users don’t have permission to view, those fields aren’t displayed on the Recommended Products page.
  4. The Product Recommendation page uses the same field set as the Product Lookup page.

Other Notes & Considerations

  1. As with the Product Search Plugin, our logic is dependent on the Quote Lines having been saved, so this will not work prior to Quick Save or Save being used on the Quote.
  2. Again note that we can also exclude any Products from being Recommended if they were already add to the Quote. This can be done by modifying the PricebookEntry query slightly as follows.

Additionally, below are some thoughts that came to mind while putting this together.  These are more train of thought areas for future exploration in real business scenarios…

  1. Loading and maintaining the Product Recommendations themselves is an interesting question as far as the best way to implement.  Initial data load based on Subject Matter Expert inputs, historical data, or the like would seem like a good starting point.  Then maintaining those Recommendations going forward becomes the important question – driving via historical usage for example, or more complex predictive approaches.
  2. Another interesting thing to plan around would be the segmentation of Product Recommendations to control behavior conditionally based on different business scenarios.  Perhaps employing date ranges, active flag, and quote/region specific flags on the Recommendations themselves would provide a good way to keep the Apex implementation generic while allowing control at the data level.
  3. For any such implementations, the usefulness of the tool often comes down to how you plan around data maintenance and scalability.  There will be a lot of unnecessary frustration and risk if you build in a way that doesn’t let data attributes drive the behavior (thus allowing you to keep the code generic and not constantly under revision).

I also wanted to say thanks to Chris Hickman for the suggestion to try out this new tool.  I think it’s clear from even this simple review that there are many potential use cases for this extension, and I’m excited to see what the future holds in this area.  Thanks for reading!

Leave a Reply

Your email address will not be published. Required fields are marked *