b2c-page-designer

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Page Designer Skill

Page Designer 技能

This skill guides you through creating custom Page Designer page types and component types for Salesforce B2C Commerce.
本技能将指导你为Salesforce B2C Commerce创建自定义的Page Designer页面类型和组件类型。

Overview

概述

Page Designer allows merchants to create and manage content pages through a visual editor. Developers create:
  1. Page Types - Define page structures with regions
  2. Component Types - Reusable content blocks with configurable attributes
Page Designer允许商家通过可视化编辑器创建和管理内容页面,开发者需要创建两类资源:
  1. 页面类型 - 定义带有区域的页面结构
  2. 组件类型 - 带有可配置属性的可复用内容块

File Structure

文件结构

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.
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
文件必须同名,
experience/pages
experience/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个字符。

Page Types

页面类型

Meta Definition (pages/homepage.json)

元定义 (pages/homepage.json)

json
{
    "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" }
            ]
        }
    ]
}
json
{
    "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" }
            ]
        }
    ]
}

Page Script (pages/homepage.js)

页面脚本 (pages/homepage.js)

javascript
'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;
};
javascript
'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;
};

Page Template (templates/experience/pages/homepage.isml)

页面模板 (templates/experience/pages/homepage.isml)

html
<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>
html
<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>

Component Types

组件类型

Meta Definition (components/banner.json)

元定义 (components/banner.json)

json
{
    "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).
json
{
    "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
                }
            ]
        }
    ]
}
组件元定义要求:必须包含
region_definitions
字段,如果组件没有嵌套区域(不支持嵌入其他组件的槽位),则赋值为
[]

Component Script (components/banner.js)

组件脚本 (components/banner.js)

javascript
'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:
javascript
function getColor(colorAttr) {
    if (!colorAttr) return '';
    if (typeof colorAttr === 'string' && colorAttr.trim()) return colorAttr.trim();
    if (colorAttr.color) return colorAttr.color;
    return '';
}
javascript
'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);          // 富文本字符串
    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" }
对象,可以使用以下工具函数兼容两种格式:
javascript
function getColor(colorAttr) {
    if (!colorAttr) return '';
    if (typeof colorAttr === 'string' && colorAttr.trim()) return colorAttr.trim();
    if (colorAttr.color) return colorAttr.color;
    return '';
}

Component Template (templates/experience/components/banner.isml)

组件模板 (templates/experience/components/banner.isml)

html
<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>
html
<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>

Attribute Types

属性类型

TypeDescriptionReturns
string
Text inputString
text
Multi-line textString
markup
Rich text editorMarkup string (use
encoding="off"
)
boolean
CheckboxBoolean
integer
Number inputInteger
enum
Single select dropdownString
image
Image pickerImage object with
file.absURL
file
File pickerFile object
url
URL pickerURL string
category
Category selectorCategory object
product
Product selectorProduct object
page
Page selectorPage object
custom
JSON object or custom editorObject (or editor-specific)
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.
类型描述返回值
string
单行文本输入框字符串
text
多行文本输入框字符串
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"
这类配置时,需要Business Manager站点的cartridge路径中包含提供该编辑器的cartridge。如果组件没有出现在编辑器中,可以先将颜色属性的类型改为
type: "string"
(让商家手动输入十六进制颜色值)测试。在脚本中建议兼容两种格式:同时支持字符串和
{ color: "#hex" }
对象(比如使用上面的
getColor(attr)
工具函数返回颜色字符串)。
default_value说明:仅用于前端店铺渲染,不会在Page Designer可视化编辑器中作为默认值预填。

Region Definitions

区域定义

json
{
    "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" }
            ]
        }
    ]
}
PropertyDescription
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
json
{
    "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" }
            ]
        }
    ]
}
属性描述
id
区域唯一标识符
name
编辑器中显示的区域名称
max_components
区域可容纳的最大组件数量(可选)
component_type_exclusions
禁止在该区域使用的组件
component_type_inclusions
仅允许在该区域使用的组件

Rendering Pages

页面渲染

In Controllers

在控制器中渲染

javascript
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();
});
javascript
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();
});

In ISML

在ISML中渲染

html
<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>
html
<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>

Component Groups

组件分组

Organize components in the editor sidebar:
json
{
    "name": "Product Card",
    "group": "products"
}
Common groups:
content
,
products
,
navigation
,
layout
,
media
可以通过分组对编辑器侧边栏的组件进行分类整理:
json
{
    "name": "Product Card",
    "group": "products"
}
常用分组:
content
(内容)、
products
(商品)、
navigation
(导航)、
layout
(布局)、
media
(媒体)

If the component does not appear in Page Designer

组件未在Page Designer中显示的排查方案

  1. Enums: Ensure all
    enum
    attributes use
    "values": ["a", "b"]
    (string array), not objects with
    value
    /
    display_value
    .
  2. Custom editors: Replace
    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.
  3. Naming: File and subdirectory names only alphanumeric or underscore.
  4. region_definitions: Component meta must include
    region_definitions
    (use
    []
    if no nested regions).
  5. Template path: Script
    Template('...')
    path must match the template path under
    templates/default/
    (including subfolders like
    experience/components/assets/...
    ).
  6. Logs: In Business Manager, Administration > Site Development > Development Setup, check error logs when opening Page Designer for script or meta errors related to your component type ID.
  7. Code version: Deploy the cartridge to the correct code version; in non-production, meta can be cached for a few seconds—try a code version switch or short wait.
  1. 枚举类型配置:确认所有
    enum
    属性的
    values
    都是字符串数组格式,没有使用
    value
    /
    display_value
    的对象格式。
  2. 自定义编辑器问题:将有问题的属性从
    type: "custom"
    (比如颜色选择器)改为
    type: "string"
    后重新部署,如果组件正常显示,说明问题大概率出在自定义编辑器或Business Manager的cartridge路径配置上。
  3. 命名规范检查:文件和子目录名称仅允许使用字母数字或下划线。
  4. region_definitions字段检查:组件元定义必须包含
    region_definitions
    字段,无嵌套区域时赋值为
    []
  5. 模板路径检查:脚本中
    Template('...')
    的路径必须与
    templates/default/
    下的模板路径完全匹配(包括
    experience/components/assets/...
    这类子文件夹路径)。
  6. 日志排查:在Business Manager的Administration > Site Development > Development Setup页面,打开Page Designer时查看错误日志,排查与你的组件类型ID相关的脚本或元定义错误。
  7. 代码版本检查:确认cartridge已经部署到正确的代码版本;非生产环境中元数据可能有几秒缓存,可以尝试切换代码版本或稍等片刻再测试。

Best Practices

最佳实践

  1. Use
    dw.util.Template
    for rendering (NOT
    dw.template.ISML
    )
  2. Keep components self-contained - avoid cross-component dependencies
  3. Provide default values for optional attributes (they apply to rendering; not shown as preselected in the editor)
  4. Group related attributes in
    attribute_definition_groups
  5. Use meaningful IDs - they're used programmatically
  6. Don't change type IDs or attribute types after merchants create content (creates inconsistency with stored data); add a new component type and deprecate the old one if needed
  7. Prefer string arrays for enums and string for colors unless you control the BM cartridge path and custom editors
  1. 渲染时使用
    dw.util.Template
    (不要使用
    dw.template.ISML
  2. 保持组件自治 - 避免组件之间的交叉依赖
  3. 为可选属性提供默认值(仅作用于渲染,不会在编辑器中预填)
  4. 使用
    attribute_definition_groups
    对相关属性进行分组
  5. 使用有语义的ID - 这些ID会被程序调用
  6. 商家创建内容后不要修改类型ID或属性类型(会导致已存储的数据不一致);如果需要修改,建议新增组件类型并废弃旧版本
  7. 优先使用字符串数组格式的枚举、字符串类型的颜色属性,除非你完全控制Business Manager的cartridge路径和自定义编辑器

Detailed Reference

详细参考

For comprehensive attribute documentation:
  • Meta Definitions Reference - Full JSON schema
  • Attribute Types Reference - All attribute types with examples
完整的属性文档请查看:
  • 元定义参考 - 完整JSON schema
  • 属性类型参考 - 所有属性类型及示例",