The hybrid approach of Salesforce CMS enables you to create and maintain content and assets within a single location. Some systems like Experience Cloud have pre-built integrations to syndicate this content. Others, like Marketing Cloud Content Builder, don’t have one. This blog will show you how to build such an integration yourself. We will be using headless Salesforce CMS and the Content Builder Block SDK for that.

Integration overview

Before we go into the implementation itself, let’s take a look at the final result. The following GIF shows how to create a new email message. Part of the message creation process is to select custom content from the left pane. The content source with the Salesforce logo represents the custom integration that is described in this blog post. By dragging the icon onto the email you get a list of images. These images are Salesforce CMS assets, which are now seamlessly integrated.

Animated GIF that shows the process of creating an email in Marketing Cloud

Our integration has three moving parts:

  • Salesforce CMS, which provides the shareable assets. This is a feature of your Salesforce org.
  • Content Builder, which is part of Marketing Cloud. Content Builder is the tool to centrally organize your Marketing Cloud assets.
  • A standalone Node.js application, which is the integration layer between Salesforce CMS and Marketing Cloud. This could be written in any programming language of your choice.

The following diagram shows the high-level implementation architecture. On the left is the Salesforce org with CMS. The Node.js application handles authentication to the org, and the CMS queries using the Delivery API, as well as rendering the output for Content Builder. The rendered output is based on the specification of the Content Builder Block SDK.

High level data flow diagram

Let’s dive deeper into the different parts.

Salesforce org and CMS preparation

As mentioned before, you use Salesforce CMS to create and maintain your assets in a central location. With CMS you can manage assets that are either private to your environments, or publicly accessible. You define this when you create a CMS channel.

The following GIF shows the basic process of defining such a channel. Once it’s created, keep the channel ID handy for a later step. The GIF shows how to get this ID via the /services/data/v51.0/connect/cms/delivery/channels API endpoint from the org. While you see how to retrieve this information using Workbench, we recommend that you use a more modern tooling like the Salesforce API Collection for Postman for this step.

Animated GIF that shows how to set up a public channel in Salesforce CMS and how to fetch the channel id using an API tool

As CMS is part of your Salesforce org, the standard mechanisms for authentication and authorization apply. To handle this, the integration uses the OAuth 2.0 JWT bearer flow, the best practice method for server-to-server communication.

The precondition for this authentication flow is a Salesforce Connected App. As you see in the following GIF, the setup of a Connected App is fully UI driven.

Animated GIF that shows how to set up a Connected App in Salesforce for server-to-server communication

If you want to learn more about Connected Apps, I highly recommend the video series on Trailhead Live. The sample application on GitHub also contains detailed step-by-step instructions for this specific setup. In the next step we will look at the Node.js application, and how to integrate it with Marketing Cloud.

Using Node.js with the Content Builder Block SDK

For providing custom content we use a custom Node.js application. This application fetches the relevant data from the Salesforce org using the Delivery API endpoints. It then builds the UI which is incorporated into Content Builder using the Block SDK. The complete source code is available on Github.

For instantiating the SDK in the Node.js application, you instantiate a new instance of the global sfdc.BlockSDK object. In the code snippet below you see such an instantiation. With the BlockSDK parameters, you control how the UI will look like in Content Builder. In our example, we define which tabs we want to make available for the user.

The real integration of the content asset, in our case an image, happens in a custom setImage() function. The next code snippet contains a shortened version of the complete function. It highlights two key elements of the Block SDK.

  • Using sdk.setContent() you have full control over the HTML that will be placed within the email. Any type of string placeholders can be passed as part of that function.
  • With sdk.setData() you specify the data to pass to Content Builder. The object that you pass over can contain any key/value pair. At runtime, the data is used to replace the placeholders from the sdk.setContent() call.

You can enrich the end user experience by also providing a custom user interface, like in the sample app. As you saw in the first GIF, the app has more features. For example, it provides a full custom user interface for the end user, where they can scale or align the image from CMS, right from within Content Builder. This is all built with HTML and JavaScript, and can be customized for your own needs.


Salesforce CMS is a great feature for providing assets from a single, central repository. With the API-first approach, you can use these assets in any target system. This makes it a perfect fit to be integrated into extensible systems, like Marketing Cloud Content builder.

Trailhead offers great resources to get started with Salesforce CMS and the Content Builder Block SDK. To get hands-on, check out the instructions on GitHub.

About the author

Raj Rao, Senior Director, Cloud Solution Alliances, Experience Cloud, Salesforce.

Raj Rao is focused on Experience Cloud Partner Enablement and Partner Success and is based in the U.S. During his 7 years at Salesforce Raj has been a Platform Solution Engineer, Community Solution Engineer, and a global lead for Sales Cloud Partner Enablement. You can connect with Raj on LinkedIn

Get the latest Salesforce Developer blog posts and podcast episodes via Slack or RSS.

Add to Slack Subscribe to RSS