Architectural Overview
Salesforce CMS powers headless digital experiences, Commerce Cloud storefronts, Marketing Cloud emails, and Experience Cloud portals. Out of the box, Salesforce CMS provides only basic content types (cms_document, cms_image, news). When enterprise content authors need structured domain models—such as Product Announcements, Executive Bios, Clinical Trial Summaries, or Promotional Banners—architects define custom CMS schemas using ContentTypeBundle.
Unlike flat XML metadata types, ContentTypeBundle is a bundle metadata type (similar to LightningComponentBundle or ExperienceBundle). It pairs an XML metadata descriptor (<DeveloperName>.contentTypeBundle-meta.xml) with a JSON schema definition (schema.json) that specifies each field’s nodeName, nodeType (TEXT, MTEXT, RTE, IMG, URL, DATE, DATETIME, NAMEFIELD), and cardinality constraints.
SFDX Directory and File Suffix
| Property | Value |
|---|---|
| Metadata Type Name | ContentTypeBundle |
| SFDX Source Directory | force-app/main/default/contentTypeBundles/<BundleName>/ |
| XML Descriptor File | <BundleName>.contentTypeBundle-meta.xml |
| Schema Definition File | schema.json |
Key Metadata Fields
The XML descriptor defines bundle-level metadata, while schema.json defines the typed content nodes.
<BundleName>.contentTypeBundle-meta.xml Fields
| Field Name | Type | Required | Architectural Purpose |
|---|---|---|---|
description |
string |
No | Explains the purpose and target channel of the custom CMS content type. |
masterLabel |
string |
Yes | The label displayed to content authors when clicking Add Content in a Salesforce CMS workspace. |
schema.json Node Properties
| Property Name | Type | Required | Architectural Purpose |
|---|---|---|---|
nodeName |
string |
Yes | Unique API identifier for the CMS field node (alphanumeric and underscores, max 80 chars). |
nodeLabel |
string |
Yes | Author-facing field label in the Salesforce CMS editor UI. |
nodeType |
string |
Yes | Field data type: NAMEFIELD (exactly one required per type), TEXT, MTEXT (multiline text), RTE (rich text), IMG (CMS image reference), URL, DATE, or DATETIME. |
required |
boolean |
No | Whether content authors must populate this field before publishing the CMS item. |
placeholderText |
string |
No | Instructional placeholder text inside the CMS input control. |
helpText |
string |
No | Tooltip guidance shown next to the field label in the CMS authoring editor. |
Declarative Bundle Example
A complete ContentTypeBundle named Product_Launch_Banner requires two files inside force-app/main/default/contentTypeBundles/Product_Launch_Banner/:
1. Metadata XML Descriptor
<?xml version="1.0" encoding="UTF-8"?>
<ContentTypeBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<description>Structured promotional banner with hero image, rich text body, and call-to-action link for Commerce and Experience Cloud sites.</description>
<masterLabel>Product Launch Banner</masterLabel>
</ContentTypeBundle>2. CMS Node Definition (schema.json)
{
"contentType": {
"developerName": "Product_Launch_Banner",
"masterLabel": "Product Launch Banner",
"description": "Structured promotional banner for Commerce and Experience Cloud.",
"nodes": [
{
"nodeName": "BannerTitle",
"nodeLabel": "Banner Headline",
"nodeType": "NAMEFIELD",
"required": true,
"placeholderText": "Enter the primary campaign headline"
},
{
"nodeName": "SubtitleText",
"nodeLabel": "Subtitle Summary",
"nodeType": "MTEXT",
"required": false,
"helpText": "Short summary displayed beneath the headline (under 200 characters)."
},
{
"nodeName": "HeroBackground",
"nodeLabel": "Hero Background Image",
"nodeType": "IMG",
"required": true
},
{
"nodeName": "PromoDetails",
"nodeLabel": "Promotional Body Copy",
"nodeType": "RTE",
"required": false
},
{
"nodeName": "CallToActionUrl",
"nodeLabel": "Call to Action URL",
"nodeType": "URL",
"required": true,
"placeholderText": "https://www.example.com/products/new"
},
{
"nodeName": "EmbargoReleaseDate",
"nodeLabel": "Embargo Release Date",
"nodeType": "DATETIME",
"required": false
}
]
}
}package.xml Manifest
<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
<types>
<members>Product_Launch_Banner</members>
<name>ContentTypeBundle</name>
</types>
<version>68.0</version>
</Package>Querying via Tooling API and SFDX CLI
You can inspect deployed custom content types via the Tooling API ManagedContentType object or retrieve the bundle files using the sf CLI.
1. Query Deployed Custom CMS Content Types via Tooling API
SELECT Id, DeveloperName, MasterLabel, Description, ManageableState, CreatedDate
FROM ManagedContentType
ORDER BY DeveloperName ASC2. SFDX CLI Retrieve and Deploy Commands
# Retrieve a specific ContentTypeBundle
sf project retrieve start --metadata ContentTypeBundle:Product_Launch_Banner
# Deploy the ContentTypeBundle directory to a target org
sf project deploy start --source-dir force-app/main/default/contentTypeBundles/Product_Launch_BannerArchitect Best Practices and Gotchas
Warning
Schema Immutability Once Content Exists: Treat ContentTypeBundle node definitions like published database columns. Once CMS authors create ManagedContent records using Product_Launch_Banner, Salesforce blocks deployments that attempt to change a node’s nodeType, delete an existing node, or flip required from false to true.
- Exactly One
NAMEFIELDNode Required: Everyschema.jsonfile must include exactly one node with"nodeType": "NAMEFIELD"and"required": true. OmittingNAMEFIELDor defining twoNAMEFIELDnodes fails deployment immediately. - 15-Node Governor Limit: A custom
ContentTypeBundlesupports at most 15 nodes per content type and 100 custom content types per org. Design cohesive, channel-agnostic content models rather than bloated 40-field schemas.
Tip
After deploying a ContentTypeBundle, map its fields to Experience Builder repeater/collection components or retrieve published items headlessly via the Connect REST API (/connect/cms/delivery/channels/<channelId>/contents/query?contentTypeFQN=Product_Launch_Banner).
Frequently asked questions
What is a ContentTypeBundle in Salesforce CMS?
ContentTypeBundle is a bundle-based Metadata API type that defines the field structure and validation rules of a custom content type in Salesforce CMS, Beyond standard News, Image, and Document types.
What files make up a ContentTypeBundle directory in SFDX?
Each ContentTypeBundle folder inside force-app/main/default/contentTypeBundles/ contains a .contentTypeBundle-meta.xml descriptor file and a schema.json file defining the CMS content nodes.
How many custom CMS content types can an org have?
An org can define up to 100 custom CMS content types across classic and enhanced CMS workspaces, with up to 15 nodes (fields) per custom content type.
Can you delete or change the data type of a node in an existing ContentTypeBundle?
Once managed content records exist for a ContentTypeBundle, you cannot change the nodeType of an existing node or remove required nodes without first archiving and deleting the dependent CMS content.
Comments
Questions, corrections and tips are welcome. Comments are GitHub Discussions, so you sign in with GitHub.