A Simple Method for Getting Default Product Images for LWC Components In B2B Commerce Using Apex and ConnectApi

Brett Dworaczyk
5 min readJul 12, 2024

--

A set of gears, one containing the Salesforce logo within
Image of gears with the Salesforce logo generated by DALL-E 3

Note: I’ve previously published an article here that outlines a method for retrieving product thumbnail images using ConnectApi and directly pulling CMS content using managed content ids. Unlike that method, the method outlined in this article does not perform any direct queries on CMS managed content itself.

The term “default”, when referring to product images, is simply a reference to the list image for the product. The list image is the image that appears in default category listings, refined/filtered product listings, and global searches. This is in contrast to the detail images that appear on the individual product detail pages. If, for your purposes, you only need product data along with associated default image data, then there is a simple way to accomplish this using ConnectApi. This particular method doesn’t require any calls to CMS channels, workspaces, or managed content.

ConnectApi

If you’ve ever taken the time to dig into the network calls being made by Salesforce’s B2BCommerce storefront (using Chrome’s Dev Tools or other means), then you may have seen the request and return payloads being utilized natively by Salesforce when viewing product category pages, search listings, etc. These exchanges vary depending on whether your site is an Aura-based site or an LWR-based site. For Aura sites, you’ll notice calls to endpoints similar to:

/sfsites/aura?r=7&aura.ApexAction.execute=1&auraXhr=true

This call results in a payload returning actions, facets, product data and metadata, etc. In an LWR site, you notice calls to endpoints similar to:

/webruntime/api/services/data/v61.0/commerce/webstores/{webstoreId}/search/products?categoryId={categoryId}&page=0&fields=Name&includeQuantityRule=true&language=en-US&asGuest=true&htmlEncode=false

This call returns a payload similar to the Aura site, which also includes category metadata, facets, product data and metadata, etc. A discerning eye will notice an important key/value pair when viewing the products array returned by these endpoints. In both Aura- and LWR-based sites, this key is defaultImage and the value is an object containing metadata for the list image displayed on the current page for any given product.

If a curious engineer were trying to make direct queries to the same endpoints seen in the network calls in these pages, they would soon find out that these cannot be queried directly by members outside Salesforce and would see errors returned concerning their lack of permissions. So how do we go about making these same queries and receiving the same payloads? The answer: ConnectApi. There are two primary ways to interact with Salesforce’s ConnectApi — via the Connect REST API for more generic integrations or the ConnectApi namespace in Apex. This article focuses on the Apex strategy.

The ConnectApi namespace contains classes that target specific systems, features, resources, and more. These classes contain static methods for querying each respective target group, and in some cases allow for data access that isn’t possible using other APIs. In our case, we’ll be looking at the CommerceCatalog class, and more specifically the getProducts() static method.

Note: ConnectApi classes make use of polymorphism to provide different method signatures that share a common method name. You’ll notice that the getProducts() method is a good example of this. There are two different method signatures available, and their use is dependent on the appropriate use-case.

Apex Implementation

Let’s take a look at Apex code that performs critical steps in retrieving and returning the default images for products. We’ll perform the following steps for these processes:

  1. Retrieve the necessary Product2 object records that satisfy our requirements
  2. Iterate over the queried Product2 object records and store the Id values for each record in a list of strings (List<String>)
  3. Define the Product2 object fields that we want to retrieve during the ConnectApi calls, initializing these during the instantiation of a list of strings
  4. Query the ConnectApi CommerceCatalog class using the aforementioned static method getProducts()
  5. Iterate over the ConnectApi.ProductOverview types contained in the ConnectApi.ProductOverviewCollection type returned from the getProducts() query and add them to a wrapper class instance
  6. Add this wrapper class instance to a list of wrapper class instances that will be returned (List<featuredProductWrapper>)

The code below shows this process in action. The static Apex class method getFeaturedProducts() includes the @AuraEnabled(cacheable=true) annotation as this method is called by an LWC component. Here is the class in its entirety:

public with sharing class B2BFeaturedProducts {
public B2BFeaturedProducts() {}

// get the featured products and associated default images
@AuraEnabled(cacheable=true)
public static List<featuredProductWrapper> getFeaturedProducts(String webStoreId) {

// initialize a list of the wrapper class type for storing return data
// NOTE: THIS IS ONLY NECESSARY BECAUSE AURA ENABLED METHODS DON'T SUPPORT THE ConnectApi.ProductOverview RETURN TYPE
List<featuredProductWrapper> productData = new List<featuredProductWrapper>();

// get all of the featured products that are currently active
List<Product2> featuredProducts = [
SELECT Id
FROM Product2
WHERE IsActive = TRUE AND Featured_Product2__c = TRUE
];

// if there are featured products
if (featuredProducts.size() > 0) {
List<String> productIds = new List<String>();

// get the product2 object ids
for (Product2 featuredProduct: featuredProducts) {
String productId = featuredProduct.Id;
productIds.add(productId);
}

// store the Product2 object fields to be retrieved via ConnectApi
List<String> productFields = new List<String>{'Name', 'Description', 'Product_Type__c'};

// get product data using ConnectApi
ConnectApi.ProductOverviewCollection productObjectdata = ConnectApi.CommerceCatalog.getProducts(webStoreId, null, productIds, null, productFields, false);

// get the ConnectApi.ProductOverview objects from the collection
for (ConnectApi.ProductOverview product: productObjectdata.products) {
productData.add(new FeaturedProductWrapper(product));
}
}

return productData;
}

// the wrapper class for returning featured product data
public class featuredProductWrapper {
@AuraEnabled
public ConnectApi.ProductOverview product { get; set; }

public featuredProductWrapper(ConnectApi.ProductOverview product) {
this.product = product;
}
}
}

Any Lightning Web Component calling the getFeaturedProducts() method only needs to pass it the web store id as a string.

In my Salesforce environment, I have a custom field on the Product2 object called Featured_Product2__c. This field is a simple checkbox that, when checked, denotes a featured product. The SOQL query in the code above queries Product2 object records that are currently active (TRUE) and have this custom field checked (TRUE). This query can obviously be tailored to your environment’s needs. As you can see in the code, I’m using a wrapper class (featuredProductWrapper) inside the outer class B2BFeaturedProducts. In many cases, when creating a single list of objects to return, this list itself can be returned to the calling entity without using a wrapper class. In this case, we need a wrapper class because Aura-enabled methods don’t support the ConnectApi.ProductOverview type as a return value (as of 2024). For this example, we can’t return a type of List<ConnectApi.ProductOverview>, but we can return a type of List<featuredProductWrapper> that contains the same data. The wrapper class in this case is used as a workaround.

As evident in the example code, pulling the “default” (list) images for products is a rather simple and quick task. By utilizing Apex and its corresponding ConnectApi namespace, this data can be pulled for any given product using a single static method call on the CommerceCatalog class. This data can then be returned to a calling LWC component.

--

--