重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
b2c-page-designer by salesforcecommercecloud/b2c-developer-tooling
npx skills add https://github.com/salesforcecommercecloud/b2c-developer-tooling --skill b2c-page-designer本技能将指导您为 Salesforce B2C Commerce 创建自定义的 Page Designer 页面类型和组件类型。
Page Designer 允许商家通过可视化编辑器创建和管理内容页面。开发者需要创建:
Page Designer 文件位于 cartridge 的 experience 目录中:
/my-cartridge
/cartridge
/experience
/pages
homepage.json # 页面类型元定义
homepage.js # 页面类型脚本
/components
banner.json # 组件类型元定义
banner.js # 组件类型脚本
/templates
/default
/experience
/pages
homepage.isml # 页面模板
/components
banner.isml # 组件模板
命名规则: .json 和 .js 文件必须具有匹配的名称。在文件名以及 或 下的任何子目录名称中,仅使用或。
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
experience/pagesexperience/components子文件夹中的组件类型: 您可以将组件元定义和脚本放在子目录中(例如 experience/components/assets/)。那么组件类型 ID 就是带点的路径:assets.hero_image_block。templates/default/ 下的模板路径必须镜像该路径(例如 templates/default/experience/components/assets/hero_image_block.isml),并且脚本必须调用 Template('experience/components/assets/hero_image_block') 以确保路径匹配。存储的 ID 为 component.{component_type_id};总长度不得超过 256 个字符。
{
"name": "主页",
"description": "包含主图和内容区域的着陆页",
"region_definitions": [
{
"id": "hero",
"name": "主图区域",
"max_components": 1
},
{
"id": "content",
"name": "主要内容"
},
{
"id": "footer",
"name": "页脚区域",
"component_type_exclusions": [
{ "type_id": "video" }
]
}
]
}
'use strict';
var Template = require('dw/util/Template');
var HashMap = require('dw/util/HashMap');
module.exports.render = function (context) {
var model = new HashMap();
var page = context.page;
model.put('page', page);
return new Template('experience/pages/homepage').render(model).text;
};
<isdecorate template="common/layout/page">
<isscript>
var PageRenderHelper = require('*/cartridge/experience/utilities/PageRenderHelper');
</isscript>
<div class="homepage">
<div class="hero-region">
<isprint value="${PageRenderHelper.renderRegion(pdict.page.getRegion('hero'))}" encoding="off"/>
</div>
<div class="content-region">
<isprint value="${PageRenderHelper.renderRegion(pdict.page.getRegion('content'))}" encoding="off"/>
</div>
<div class="footer-region">
<isprint value="${PageRenderHelper.renderRegion(pdict.page.getRegion('footer'))}" encoding="off"/>
</div>
</div>
</isdecorate>
{
"name": "横幅",
"description": "带有图片和行动号召的推广横幅",
"group": "content",
"region_definitions": [],
"attribute_definition_groups": [
{
"id": "image",
"name": "图片设置",
"attribute_definitions": [
{
"id": "image",
"name": "横幅图片",
"type": "image",
"required": true
},
{
"id": "alt",
"name": "替代文本",
"type": "string",
"required": true
}
]
},
{
"id": "content",
"name": "内容",
"attribute_definitions": [
{
"id": "headline",
"name": "标题",
"type": "string",
"required": true
},
{
"id": "body",
"name": "正文",
"type": "markup"
},
{
"id": "ctaUrl",
"name": "行动号召链接",
"type": "url"
},
{
"id": "ctaText",
"name": "行动号召按钮文本",
"type": "string"
}
]
},
{
"id": "layout",
"name": "布局选项",
"attribute_definitions": [
{
"id": "alignment",
"name": "文本对齐",
"type": "enum",
"values": ["left", "center", "right"],
"default_value": "center"
},
{
"id": "fullWidth",
"name": "全宽",
"type": "boolean",
"default_value": false
}
]
}
]
}
组件元定义: 必须始终包含 region_definitions。当组件没有嵌套区域(没有其他组件的插槽)时,使用 []。
'use strict';
var Template = require('dw/util/Template');
var HashMap = require('dw/util/HashMap');
var URLUtils = require('dw/web/URLUtils');
module.exports.render = function (context) {
var model = new HashMap();
var content = context.content;
// 访问商家配置的属性
model.put('image', content.image); // 图片对象
model.put('alt', content.alt); // 字符串
model.put('headline', content.headline); // 字符串
model.put('body', content.body); // Markup 字符串
model.put('ctaUrl', content.ctaUrl); // URL 对象
model.put('ctaText', content.ctaText); // 字符串
model.put('alignment', content.alignment || 'center');
model.put('fullWidth', content.fullWidth);
return new Template('experience/components/banner').render(model).text;
};
模板路径: 传递给 Template(...) 的路径必须与 templates/default/ 下的模板路径匹配。如果组件位于子文件夹中(例如 experience/components/assets/hero_image_block),请使用 Template('experience/components/assets/hero_image_block') 并将 ISML 文件放置在 templates/default/experience/components/assets/hero_image_block.isml。
处理颜色(字符串或颜色选择器对象): 如果一个属性可以是十六进制字符串或颜色选择器对象 { color: "#hex" },请使用一个小型辅助函数,使脚本能同时处理两者:
function getColor(colorAttr) {
if (!colorAttr) return '';
if (typeof colorAttr === 'string' && colorAttr.trim()) return colorAttr.trim();
if (colorAttr.color) return colorAttr.color;
return '';
}
<div class="banner ${pdict.fullWidth ? 'banner--full-width' : ''}"
style="text-align: ${pdict.alignment}">
<isif condition="${pdict.image}">
<img src="${pdict.image.file.absURL}"
alt="${pdict.alt}"
class="banner__image"/>
</isif>
<div class="banner__content">
<h2 class="banner__headline">${pdict.headline}</h2>
<isif condition="${pdict.body}">
<div class="banner__body">
<isprint value="${pdict.body}" encoding="off"/>
</div>
</isif>
<isif condition="${pdict.ctaUrl && pdict.ctaText}">
<a href="${pdict.ctaUrl}" class="banner__cta btn btn-primary">
${pdict.ctaText}
</a>
</isif>
</div>
</div>
| 类型 | 描述 | 返回值 |
|---|---|---|
string | 文本输入框 | 字符串 |
text | 多行文本 | 字符串 |
markup | 富文本编辑器 | Markup 字符串(使用 encoding="off") |
boolean | 复选框 | 布尔值 |
integer | 数字输入框 | 整数 |
enum | 单选下拉框 | 字符串 |
image | 图片选择器 | 包含 file.absURL 的图片对象 |
file | 文件选择器 | 文件对象 |
url | URL 选择器 | URL 字符串 |
category | 分类选择器 | 分类对象 |
product | 产品选择器 | 产品对象 |
page | 页面选择器 | 页面对象 |
custom | JSON 对象或自定义编辑器 | 对象(或编辑器特定类型) |
枚举类型 - 对组件可见性至关重要: 为 values 使用字符串数组:"values": ["left", "center", "right"]。不要使用像 { "value": "x", "display_value": "X" } 这样的对象;这种格式可能导致组件类型被拒绝,并且不会出现在 Page Designer 的组件列表中。
自定义类型和颜色: 使用 type: "custom" 并配合例如 editor_definition.type: "styling.colorPicker",这需要一个在业务管理器站点 cartridge 路径上提供该编辑器的 cartridge。如果组件没有出现在编辑器中,请对颜色属性使用 type: "string"(商家输入十六进制值)。在脚本中,支持两者:接受字符串或像 { color: "#hex" } 这样的对象(例如,使用一个小的 getColor(attr) 辅助函数来返回字符串)。
default_value: 仅用于店面渲染;它不会在 Page Designer 可视化编辑器中显示为预选值。
{
"region_definitions": [
{
"id": "main",
"name": "主要内容",
"max_components": 10,
"component_type_exclusions": [
{ "type_id": "heavy-component" }
],
"component_type_inclusions": [
{ "type_id": "text-block" },
{ "type_id": "image-block" }
]
}
]
}
| 属性 | 描述 |
|---|---|
id | 唯一的区域标识符 |
name | 编辑器中的显示名称 |
max_components | 最大组件数量(可选) |
component_type_exclusions | 不允许使用的组件 |
component_type_inclusions | 只允许这些组件 |
var PageMgr = require('dw/experience/PageMgr');
server.get('Show', function (req, res, next) {
var page = PageMgr.getPage(req.querystring.cid);
if (page && page.isVisible()) {
res.page(page.ID);
} else {
res.setStatusCode(404);
res.render('error/notfound');
}
next();
});
<isscript>
var PageMgr = require('dw/experience/PageMgr');
var page = PageMgr.getPage('homepage-id');
</isscript>
<isif condition="${page && page.isVisible()}">
<isprint value="${PageMgr.renderPage(page.ID)}" encoding="off"/>
</isif>
在编辑器侧边栏中组织组件:
{
"name": "产品卡片",
"group": "products"
}
常用分组:content, products, navigation, layout, media
enum 属性都使用 "values": ["a", "b"](字符串数组),而不是带有 value/display_value 的对象。type: "custom"(例如颜色选择器)替换为 type: "string" 并重新部署;如果组件随后出现,问题很可能出在自定义编辑器或业务管理器 cartridge 路径上。region_definitions(如果没有嵌套区域,请使用 [])。Template('...') 路径必须与 templates/default/ 下的模板路径匹配(包括像 experience/components/assets/... 这样的子文件夹)。dw.util.Template 进行渲染(不要使用 dw.template.ISML)attribute_definition_groups 中分组相关属性有关全面的属性文档,请参阅:
每周安装数
65
代码仓库
GitHub 星标数
34
首次出现
2026年2月21日
安全审计
已安装于
github-copilot60
codex57
cursor57
opencode56
gemini-cli55
amp55
This skill guides you through creating custom Page Designer page types and component types for Salesforce B2C Commerce.
Page Designer allows merchants to create and manage content pages through a visual editor. Developers create:
Page Designer files are in the cartridge's experience directory:
/my-cartridge
/cartridge
/experience
/pages
homepage.json # Page type meta definition
homepage.js # Page type script
/components
banner.json # Component type meta definition
banner.js # Component type script
/templates
/default
/experience
/pages
homepage.isml # Page template
/components
banner.isml # Component template
Naming: The .json and .js files must have matching names. Use only alphanumeric or underscore in file names and in any subdirectory names under experience/pages or experience/components.
Component types in subfolders: You can put component meta and script in a subdirectory (e.g. experience/components/assets/). The component type ID is then the path with dots: assets.hero_image_block. The template path under templates/default/ must mirror that path (e.g. templates/default/experience/components/assets/hero_image_block.isml), and the script must call Template('experience/components/assets/hero_image_block') so the path matches. Stored ID is component.{component_type_id}; total length must not exceed 256 characters.
{
"name": "Home Page",
"description": "Landing page with hero and content regions",
"region_definitions": [
{
"id": "hero",
"name": "Hero Section",
"max_components": 1
},
{
"id": "content",
"name": "Main Content"
},
{
"id": "footer",
"name": "Footer Section",
"component_type_exclusions": [
{ "type_id": "video" }
]
}
]
}
'use strict';
var Template = require('dw/util/Template');
var HashMap = require('dw/util/HashMap');
module.exports.render = function (context) {
var model = new HashMap();
var page = context.page;
model.put('page', page);
return new Template('experience/pages/homepage').render(model).text;
};
<isdecorate template="common/layout/page">
<isscript>
var PageRenderHelper = require('*/cartridge/experience/utilities/PageRenderHelper');
</isscript>
<div class="homepage">
<div class="hero-region">
<isprint value="${PageRenderHelper.renderRegion(pdict.page.getRegion('hero'))}" encoding="off"/>
</div>
<div class="content-region">
<isprint value="${PageRenderHelper.renderRegion(pdict.page.getRegion('content'))}" encoding="off"/>
</div>
<div class="footer-region">
<isprint value="${PageRenderHelper.renderRegion(pdict.page.getRegion('footer'))}" encoding="off"/>
</div>
</div>
</isdecorate>
{
"name": "Banner",
"description": "Promotional banner with image and CTA",
"group": "content",
"region_definitions": [],
"attribute_definition_groups": [
{
"id": "image",
"name": "Image Settings",
"attribute_definitions": [
{
"id": "image",
"name": "Banner Image",
"type": "image",
"required": true
},
{
"id": "alt",
"name": "Alt Text",
"type": "string",
"required": true
}
]
},
{
"id": "content",
"name": "Content",
"attribute_definitions": [
{
"id": "headline",
"name": "Headline",
"type": "string",
"required": true
},
{
"id": "body",
"name": "Body Text",
"type": "markup"
},
{
"id": "ctaUrl",
"name": "CTA Link",
"type": "url"
},
{
"id": "ctaText",
"name": "CTA Button Text",
"type": "string"
}
]
},
{
"id": "layout",
"name": "Layout Options",
"attribute_definitions": [
{
"id": "alignment",
"name": "Text Alignment",
"type": "enum",
"values": ["left", "center", "right"],
"default_value": "center"
},
{
"id": "fullWidth",
"name": "Full Width",
"type": "boolean",
"default_value": false
}
]
}
]
}
Component meta: Always include region_definitions. Use [] when the component has no nested regions (no slots for other components).
'use strict';
var Template = require('dw/util/Template');
var HashMap = require('dw/util/HashMap');
var URLUtils = require('dw/web/URLUtils');
module.exports.render = function (context) {
var model = new HashMap();
var content = context.content;
// Access merchant-configured attributes
model.put('image', content.image); // Image object
model.put('alt', content.alt); // String
model.put('headline', content.headline); // String
model.put('body', content.body); // Markup string
model.put('ctaUrl', content.ctaUrl); // URL object
model.put('ctaText', content.ctaText); // String
model.put('alignment', content.alignment || 'center');
model.put('fullWidth', content.fullWidth);
return new Template('experience/components/banner').render(model).text;
};
Template path: The path passed to Template(...) must match the template path under templates/default/. If the component lives in a subfolder (e.g. experience/components/assets/hero_image_block), use Template('experience/components/assets/hero_image_block') and place the ISML at templates/default/experience/components/assets/hero_image_block.isml.
Handling colors (string or color picker object): If an attribute can be a hex string or a color picker object { color: "#hex" }, use a small helper so the script works with both:
function getColor(colorAttr) {
if (!colorAttr) return '';
if (typeof colorAttr === 'string' && colorAttr.trim()) return colorAttr.trim();
if (colorAttr.color) return colorAttr.color;
return '';
}
<div class="banner ${pdict.fullWidth ? 'banner--full-width' : ''}"
style="text-align: ${pdict.alignment}">
<isif condition="${pdict.image}">
<img src="${pdict.image.file.absURL}"
alt="${pdict.alt}"
class="banner__image"/>
</isif>
<div class="banner__content">
<h2 class="banner__headline">${pdict.headline}</h2>
<isif condition="${pdict.body}">
<div class="banner__body">
<isprint value="${pdict.body}" encoding="off"/>
</div>
</isif>
<isif condition="${pdict.ctaUrl && pdict.ctaText}">
<a href="${pdict.ctaUrl}" class="banner__cta btn btn-primary">
${pdict.ctaText}
</a>
</isif>
</div>
</div>
| Type | Description | Returns |
|---|---|---|
string | Text input | String |
text | Multi-line text | String |
markup | Rich text editor | Markup string (use encoding="off") |
boolean | Checkbox | Boolean |
integer |
Enum — critical for component visibility: Use a string array for values: "values": ["left", "center", "right"]. Do not use objects like { "value": "x", "display_value": "X" }; that format can cause the component type to be rejected and not appear in the Page Designer component list.
Custom and colors: type: "custom" with e.g. editor_definition.type: "styling.colorPicker" requires a cartridge that provides that editor on the Business Manager site cartridge path. If the component does not show up in the editor, use type: "string" for color attributes (merchant types a hex). In the script, support both: accept a string or an object like { color: "#hex" } (e.g. a small getColor(attr) helper that returns the string).
default_value: Used for storefront rendering only; it is not shown as preselected in the Page Designer visual editor.
{
"region_definitions": [
{
"id": "main",
"name": "Main Content",
"max_components": 10,
"component_type_exclusions": [
{ "type_id": "heavy-component" }
],
"component_type_inclusions": [
{ "type_id": "text-block" },
{ "type_id": "image-block" }
]
}
]
}
| Property | Description |
|---|---|
id | Unique region identifier |
name | Display name in editor |
max_components | Max number of components (optional) |
component_type_exclusions | Components NOT allowed |
component_type_inclusions | Only these components allowed |
var PageMgr = require('dw/experience/PageMgr');
server.get('Show', function (req, res, next) {
var page = PageMgr.getPage(req.querystring.cid);
if (page && page.isVisible()) {
res.page(page.ID);
} else {
res.setStatusCode(404);
res.render('error/notfound');
}
next();
});
<isscript>
var PageMgr = require('dw/experience/PageMgr');
var page = PageMgr.getPage('homepage-id');
</isscript>
<isif condition="${page && page.isVisible()}">
<isprint value="${PageMgr.renderPage(page.ID)}" encoding="off"/>
</isif>
Organize components in the editor sidebar:
{
"name": "Product Card",
"group": "products"
}
Common groups: content, products, navigation, layout, media
enum attributes use "values": ["a", "b"] (string array), not objects with value/display_value.type: "custom" (e.g. color picker) with type: "string" for the problematic attributes and redeploy; if the component then appears, the issue is likely the custom editor or BM cartridge path.region_definitions (use [] if no nested regions).dw.util.Template for rendering (NOT dw.template.ISML)attribute_definition_groupsFor comprehensive attribute documentation:
Weekly Installs
65
Repository
GitHub Stars
34
First Seen
Feb 21, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
github-copilot60
codex57
cursor57
opencode56
gemini-cli55
amp55
Azure RBAC 权限管理工具:查找最小角色、创建自定义角色与自动化分配
148,200 周安装
通用项目发布工具 - 多语言更新日志自动生成 | 支持Node.js/Python/Rust/Claude插件
62 周安装
Edge Pipeline Orchestrator:自动化金融交易策略流水线编排工具
62 周安装
Python ROI 计算器:投资回报率、营销ROI、盈亏平衡分析工具
62 周安装
Salesforce Hyperforce 2025架构指南:云原生、零信任安全与开发最佳实践
62 周安装
PowerShell 2025 重大变更与迁移指南:版本移除、模块停用、WMIC替代方案
62 周安装
2025安全优先Bash脚本编写指南:输入验证、命令注入与路径遍历防护
62 周安装
| Number input |
| Integer |
enum | Single select dropdown | String |
image | Image picker | Image object with file.absURL |
file | File picker | File object |
url | URL picker | URL string |
category | Category selector | Category object |
product | Product selector | Product object |
page | Page selector | Page object |
custom | JSON object or custom editor | Object (or editor-specific) |
Template('...')templates/default/experience/components/assets/...