When working with Salesforce CMS Content (Managed Content Object) and attempting to retrieve content using Apex, you may encounter the following error when utilizing methods from the ManagedContent Class methods such as getManagedContentByContentKeys or getManagedContentByIds.
ConnectApi.ConnectApiException:[errorCode=INSUFFICIENT_ACCESS]: "You don't have access to this channel."
This error occurs because of ConnectAPI.ManagedContent is designed for regular workspace APIs. For enhanced workspaces (and channels), you should use the ConnectAPI.ManagedContentDelivery class instead.
Here’s an example of how to use Lightning Web Components (LWC) to display CMS Content (Collection) as an accordion on a public Lightning Web Runtime (LWR) site, leveraging the ConnectAPI.ManagedContentDelivery class. In this example, I use the getCollectionItemsForChannel method from the ConnectAPI.ManagedContentDelivery class to retrieve CMS Collection items and display them as an accordion. Additionally, you might come across an error when parsing and passing data from Apex to LWC, as described in this known issue. The article also provides a workaround for this problem:
public without sharing class CmsCollectionAccordionLWC {
@AuraEnabled(cacheable=false)
public static List<CMSContent> getCMSContentByContentKey(String ContentKey){
try{
system.debug('ContentKey: '+ContentKey);
String channelId = ConnectApi.ManagedContent.getAllDeliveryChannels(0, 1).channels[0].channelId;
system.debug('channelId: '+channelId);
ConnectApi.ManagedContentCollectionItems data = ConnectApi.ManagedContentDelivery.getCollectionItemsForChannel(channelId, contentKey, NULL);
List<CMSContent> CMSContents = getCollectionCMSContent(data, channelId);
system.debug('return data: '+CMSContents);
return CMSContents;
}catch(Exception e) {
throw new AuraHandledException(e.getMessage());
}
}
public static List<CMSContent> getCollectionCMSContent(ConnectApi.ManagedContentCollectionItems data, String channelId) {
try {
List<CMSContent> CMSContents = new List<CMSContent>();
CMSContent CMSContentObj = new CMSContent();
CMSContentObj.contentKey = data.collectionKey;
CMSContentObj.contentType = data.collectionType.fullyQualifiedName;
CMSContentObj.mainTitle = data.title;
//CMSContentObj.unauthenticatedUrl = data.urlName;
list<ContentItem> ContentItemsObjList = new list<ContentItem> ();
for(ConnectApi.ManagedContentCollectionItem item: data.items){
ContentItem ContentItemObj = new ContentItem();
ContentItemObj.key = item.id;
ContentItemObj.title = item.name;
ConnectApi.ManagedContentDeliveryDocument newsData = ConnectApi.ManagedContentDelivery.getManagedContentForChannel(channelId, item.id, true);
String.ValueOf(newsData?.contentBody?.get('body'));
ContentItemObj.body = String.ValueOf(newsData?.contentBody?.get('body'));
ContentItemsObjList.add(ContentItemObj);
}
CMSContentObj.ContentItems = ContentItemsObjList;
CMSContents.add(CMSContentObj);
return CMSContents;
}catch(Exception e) {
throw new AuraHandledException(e.getMessage());
}
}
public class CMSContent {
@AuraEnabled public string contentKey {get; set;}
@AuraEnabled public string contentType {get; set;}
@AuraEnabled public String mainTitle {get; set;}
@AuraEnabled public list<ContentItem> ContentItems {get; set;}
}
public class ContentItem {
@AuraEnabled public string key {get; set;}
@AuraEnabled public String title {get; set;}
@AuraEnabled public String body {get; set;}
}
}
Lightning Web Component
<template>
<lightning-card title="CMS Content Collection" icon-name="standard:question_feed">
<!-- ERROR PANEL START-->
<template if:true={error}>
<div class="slds-var-m-vertical_small">
<span class="slds-text-color_destructive">
{errorTitle}.
</span>
<template if:true={errorMessages.length}>
<template for:each={errorMessages} for:item="message">
<p class="slds-text-body_regular" key={message}>
{message}
</p>
</template>
</template>
</div>
</template>
<!-- ERROR PANEL END-->
<template if:true={isLoaded}>
<lightning-accordion allow-multiple-sections-open title="ContentData.mainTitle">
<template for:each={contentData} for:item="faq">
<template for:each={faq.ContentItems} for:item="item">
<lightning-accordion-section key={item.contentKey} name={item.contentKey} label={item.title}>
<div class="slds-var-m-horizontal_small">
<lightning-formatted-rich-text value={item.body}></lightning-formatted-rich-text>
</div>
</lightning-accordion-section>
</template>
</template>
</lightning-accordion>
</template>
<template if:false={isLoaded}>
<div class="slds-align_absolute-center">
<lightning-spinner alternative-text="Loading CMS content..." size="large"></lightning-spinner>
</div>
</template>
</lightning-card>
</template>
import { LightningElement, api, wire } from 'lwc';
import getCMSContentByContentKey from '@salesforce/apex/CmsCollectionAccordionLWC.getCMSContentByContentKey';
export default class CmsCollectionAccordion extends LightningElement {
@api contentId;
contentData;
isLoaded = false;
error;
errorTitle;
connectedCallback() {
getCMSContentByContentKey({ ContentKey: this.contentId })
.then(result => {
console.log('result1::', result);
this.contentData = result;
this.isLoaded = true;
})
.catch(error => {
console.log('error1::', JSON.stringify(error));
console.log('contentId::', JSON.stringify(this.contentId));
this.error = error;
this.errorTitle = 'Error retreiving CMS Content!';
this.isLoaded = true;
});
}
get errorMessages() {
return this.reduceErrors(this.error);
}
reduceErrors(errors) {
if (!Array.isArray(errors)) {
errors = [errors];
}
return (
errors
// Remove null/undefined items
.filter((error) => !!error)
// Extract an error message
.map((error) => {
// UI API read errors
if (Array.isArray(error.body)) {
return error.body.map((e) => e.message);
}
// UI API DML, Apex and network errors
else if (error.body && typeof error.body.message === 'string') {
return error.body.message;
}
// JS errors
else if (typeof error.message === 'string') {
return error.message;
}
// Unknown error shape so try HTTP status text
return error.statusText;
})
// Flatten
.reduce((prev, curr) => prev.concat(curr), [])
// Remove empty strings
.filter((message) => !!message)
);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>60.0</apiVersion>
<isExposed>true</isExposed>
<masterLabel>CMS Collection As Accordion</masterLabel>
<description>This component gets Collection CMS content as input and display the collection items as accordion.</description>
<targets>
<target>lightningCommunity__Page</target>
<target>lightningCommunity__Default</target>
</targets>
<targetConfigs>
<targetConfig targets="lightningCommunity__Default">
<property type="ContentReference" name="contentId" label="Collection Content" />
</targetConfig>
</targetConfigs>
</LightningComponentBundle>
Happy coding 😎