unlayer-config
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseConfigure the Editor
配置Unlayer编辑器
Overview
概述
Unlayer's behavior is controlled through options and runtime methods. This skill covers features, appearance, dynamic content, security, and file storage.
unlayer.init()Where to find keys:
- Project ID — Dashboard > Project > Settings
- Project Secret (for HMAC) — Dashboard > Project > Settings > API Keys
- Cloud API Key (for image/PDF export) — Dashboard > Project > Settings > API Keys
Dashboard: console.unlayer.com
Unlayer的行为由选项和运行时方法控制。本技能涵盖功能、外观、动态内容、安全和文件存储方面的配置。
unlayer.init()密钥位置:
- 项目ID — 控制台 > 项目 > 设置
- 项目密钥(用于HMAC) — 控制台 > 项目 > 设置 > API密钥
- 云API密钥(用于图片/PDF导出) — 控制台 > 项目 > 设置 > API密钥
Feature Flags
功能开关
Control what's available in the editor:
javascript
unlayer.init({
features: {
audit: true, // Content validation
preview: true, // Preview button
undoRedo: true, // Undo/redo
stockImages: true, // Stock photo library
userUploads: true, // User upload tab
preheaderText: true, // Email preheader field
textEditor: {
spellChecker: true,
tables: false, // Tables inside text blocks
cleanPaste: 'confirm', // true | false | 'basic' | 'confirm'
emojis: true,
},
// Paid features:
ai: true, // AI text generation
collaboration: false, // Real-time collaboration
sendTestEmail: false, // Test email button
},
});See references/feature-flags.md for all flags (AI sub-options, image editor, color picker, etc.).
控制编辑器中可用的功能:
javascript
unlayer.init({
features: {
audit: true, // 内容验证
preview: true, // 预览按钮
undoRedo: true, // 撤销/重做
stockImages: true, // 库存图片库
userUploads: true, // 用户上传标签页
preheaderText: true, // 邮件预头字段
textEditor: {
spellChecker: true,
tables: false, // 文本块内的表格
cleanPaste: 'confirm', // true | false | 'basic' | 'confirm'
emojis: true,
},
// 付费功能:
ai: true, // AI文本生成
collaboration: false, // 实时协作
sendTestEmail: false, // 测试邮件按钮
},
});查看references/feature-flags.md获取所有开关(AI子选项、图片编辑器、颜色选择器等)。
Appearance & Theming
外观与主题
javascript
unlayer.init({
appearance: {
theme: 'modern_dark', // 'modern_light' | 'modern_dark' | 'classic_light' | 'classic_dark'
panels: {
tools: {
dock: 'right', // 'left' | 'right' (default: 'right')
collapsible: true,
},
},
actionBar: {
placement: 'top', // 'top' | 'bottom' | 'top_left' | 'top_right' | 'bottom_left' | 'bottom_right'
},
},
});
// Change at runtime:
unlayer.setAppearance({ theme: 'modern_dark' });
// Or just the theme:
unlayer.setTheme('modern_dark');javascript
unlayer.init({
appearance: {
theme: 'modern_dark', // 'modern_light' | 'modern_dark' | 'classic_light' | 'classic_dark'
panels: {
tools: {
dock: 'right', // 'left' | 'right'(默认值:'right')
collapsible: true,
},
},
actionBar: {
placement: 'top', // 'top' | 'bottom' | 'top_left' | 'top_right' | 'bottom_left' | 'bottom_right'
},
},
});
// 运行时更改:
unlayer.setAppearance({ theme: 'modern_dark' });
// 或仅更改主题:
unlayer.setTheme('modern_dark');Custom Fonts
自定义字体
javascript
unlayer.init({
fonts: {
showDefaultFonts: true,
customFonts: [{
label: 'Poppins',
value: "'Poppins', sans-serif",
url: 'https://fonts.googleapis.com/css?family=Poppins:400,700',
weights: [400, 700], // or [{ label: 'Regular', value: 400 }, { label: 'Bold', value: 700 }]
}],
},
});javascript
unlayer.init({
fonts: {
showDefaultFonts: true,
customFonts: [{
label: 'Poppins',
value: "'Poppins', sans-serif",
url: 'https://fonts.googleapis.com/css?family=Poppins:400,700',
weights: [400, 700], // 或 [{ label: 'Regular', value: 400 }, { label: 'Bold', value: 700 }]
}],
},
});Tab Configuration
标签页配置
javascript
unlayer.init({
tabs: {
content: { enabled: true, position: 1 },
blocks: { enabled: true, position: 2 },
body: { enabled: true, position: 3 },
images: { enabled: true, position: 4 },
uploads: { enabled: true, position: 5 },
},
});javascript
unlayer.init({
tabs: {
content: { enabled: true, position: 1 },
blocks: { enabled: true, position: 2 },
body: { enabled: true, position: 3 },
images: { enabled: true, position: 4 },
uploads: { enabled: true, position: 5 },
},
});Merge Tags
Merge Tags
Merge tags are placeholders replaced at send time (e.g., ). They use your template engine's syntax (Handlebars, Liquid, Jinja, etc.):
{{first_name}}javascript
unlayer.setMergeTags({
first_name: {
name: 'First Name',
value: '{{first_name}}', // Your template syntax
sample: 'John', // Shown in editor preview
},
last_name: {
name: 'Last Name',
value: '{{last_name}}',
sample: 'Doe',
},
company: {
name: 'Company', // Nested group
mergeTags: {
name: { name: 'Company Name', value: '{{company.name}}', sample: 'Acme Inc' },
logo: { name: 'Logo URL', value: '{{company.logo}}' },
},
},
products: {
name: 'Products',
rules: {
repeat: {
name: 'Repeat for Each Product',
before: '{{#each products}}', // Loop start — syntax depends on your template engine
after: '{{/each}}', // Loop end
sample: true, // Show sample data in editor
},
},
mergeTags: {
name: { name: 'Product Name', value: '{{this.name}}' },
price: { name: 'Price', value: '{{this.price}}' },
image: { name: 'Image URL', value: '{{this.image}}' },
},
},
});
// Autocomplete trigger (optional)
unlayer.setMergeTagsConfig({ autocompleteTriggerChar: '{{', sort: true });Merge Tags是发送时会被替换的占位符(例如)。它们使用你的模板引擎语法(Handlebars、Liquid、Jinja等):
{{first_name}}javascript
unlayer.setMergeTags({
first_name: {
name: 'First Name',
value: '{{first_name}}', // 你的模板语法
sample: 'John', // 编辑器预览中显示的示例
},
last_name: {
name: 'Last Name',
value: '{{last_name}}',
sample: 'Doe',
},
company: {
name: 'Company', // 嵌套组
mergeTags: {
name: { name: 'Company Name', value: '{{company.name}}', sample: 'Acme Inc' },
logo: { name: 'Logo URL', value: '{{company.logo}}' },
},
},
products: {
name: 'Products',
rules: {
repeat: {
name: 'Repeat for Each Product',
before: '{{#each products}}', // 循环开始——语法取决于你的模板引擎
after: '{{/each}}', // 循环结束
sample: true, // 在编辑器中显示示例数据
},
},
mergeTags: {
name: { name: 'Product Name', value: '{{this.name}}' },
price: { name: 'Price', value: '{{this.price}}' },
image: { name: 'Image URL', value: '{{this.image}}' },
},
},
});
// 自动补全触发(可选)
unlayer.setMergeTagsConfig({ autocompleteTriggerChar: '{{', sort: true });Design Tags (Editor-Only Placeholders)
设计标签(仅编辑器占位符)
Design tags are replaced in the editor UI but NOT in exports — useful for showing personalized content to the template author:
javascript
unlayer.setDesignTags({
business_name: 'Acme Corp',
current_user_name: 'Jane Smith',
});
unlayer.setDesignTagsConfig({ delimiter: ['{{', '}}'] });设计标签会在编辑器UI中被替换,但不会在导出内容中替换——适用于向模板作者显示个性化内容:
javascript
unlayer.setDesignTags({
business_name: 'Acme Corp',
current_user_name: 'Jane Smith',
});
unlayer.setDesignTagsConfig({ delimiter: ['{{', '}}'] });Display Conditions (Paid)
显示条件(付费)
Wrap content in conditional blocks for your template engine:
javascript
unlayer.setDisplayConditions([
{
type: 'segment',
label: 'VIP Customers',
description: 'Only shown to VIP segment',
before: '{% if customer.vip %}', // Your template engine syntax
after: '{% endif %}',
},
{
type: 'segment',
label: 'New Subscribers',
description: 'First 30 days only',
before: '{% if subscriber.age_days < 30 %}',
after: '{% endif %}',
},
]);为你的模板引擎用条件块包裹内容:
javascript
unlayer.setDisplayConditions([
{
type: 'segment',
label: 'VIP Customers',
description: '仅对VIP用户组显示',
before: '{% if customer.vip %}', // 你的模板引擎语法
after: '{% endif %}',
},
{
type: 'segment',
label: '新订阅用户',
description: '仅显示给订阅30天内的用户',
before: '{% if subscriber.age_days < 30 %}',
after: '{% endif %}',
},
]);Special Links
特殊链接
Pre-defined links users can insert (unsubscribe, preferences, etc.):
javascript
unlayer.setSpecialLinks({
unsubscribe: {
name: 'Unsubscribe',
href: '{{unsubscribe_url}}',
target: '_blank',
},
preferences: {
name: 'Preferences',
specialLinks: {
email_prefs: { name: 'Email Preferences', href: '{{preferences_url}}' },
profile: { name: 'Profile Settings', href: '{{profile_url}}' },
},
},
});用户可插入的预定义链接(退订、偏好设置等):
javascript
unlayer.setSpecialLinks({
unsubscribe: {
name: '退订',
href: '{{unsubscribe_url}}',
target: '_blank',
},
preferences: {
name: '偏好设置',
specialLinks: {
email_prefs: { name: '邮件偏好', href: '{{preferences_url}}' },
profile: { name: '个人资料设置', href: '{{profile_url}}' },
},
},
});HMAC Security
HMAC安全
Prevents users from impersonating each other. Generate the HMAC signature server-side using your Project Secret (Dashboard > Project > Settings > API Keys):
Node.js:
javascript
const crypto = require('crypto');
const signature = crypto
.createHmac('sha256', 'YOUR_PROJECT_SECRET') // From Dashboard
.update(String(userId))
.digest('hex');Python/Django:
python
import hmac, hashlib
signature = hmac.new(
b'YOUR_PROJECT_SECRET',
bytes(str(request.user.id), encoding='utf-8'),
digestmod=hashlib.sha256
).hexdigest()Client-side — pass the server-generated signature:
javascript
unlayer.init({
user: {
id: userId, // Must match what you signed
signature: signatureFromServer, // HMAC from your backend
name: 'John Doe', // Optional
email: 'john@acme.com', // Optional
},
});See references/security.md for Ruby and PHP examples.
防止用户冒充。使用你的项目密钥(控制台 > 项目 > 设置 > API密钥)在服务端生成HMAC签名:
Node.js:
javascript
const crypto = require('crypto');
const signature = crypto
.createHmac('sha256', 'YOUR_PROJECT_SECRET') // 来自控制台
.update(String(userId))
.digest('hex');Python/Django:
python
import hmac, hashlib
signature = hmac.new(
b'YOUR_PROJECT_SECRET',
bytes(str(request.user.id), encoding='utf-8'),
digestmod=hashlib.sha256
).hexdigest()客户端 — 传入服务端生成的签名:
javascript
unlayer.init({
user: {
id: userId, // 必须与你签名的ID一致
signature: signatureFromServer, // 来自后端的HMAC
name: 'John Doe', // 可选
email: 'john@acme.com', // 可选
},
});查看references/security.md获取Ruby和PHP示例。
File Storage & Image Upload
文件存储与图片上传
Custom Upload (Your Server)
自定义上传(你的服务器)
javascript
unlayer.registerCallback('image', (file, done) => {
const data = new FormData();
data.append('file', file.attachments[0]);
fetch('/api/uploads', { method: 'POST', body: data })
.then((r) => {
if (!r.ok) throw new Error('Upload failed');
return r.json();
})
.then((result) => done({ progress: 100, url: result.url }))
.catch((err) => console.error('Upload error:', err));
});Your backend should return:
json
{ "url": "https://your-cdn.com/images/uploaded-file.png" }javascript
unlayer.registerCallback('image', (file, done) => {
const data = new FormData();
data.append('file', file.attachments[0]);
fetch('/api/uploads', { method: 'POST', body: data })
.then((r) => {
if (!r.ok) throw new Error('上传失败');
return r.json();
})
.then((result) => done({ progress: 100, url: result.url }))
.catch((err) => console.error('上传错误:', err));
});你的后端应返回:
json
{ "url": "https://your-cdn.com/images/uploaded-file.png" }File Manager (Browse Uploaded Images)
文件管理器(浏览已上传图片)
Requires in init — images are scoped per user:
user.idjavascript
unlayer.init({
user: { id: 123 }, // Required for file manager
features: { userUploads: { enabled: true, search: true } },
});
unlayer.registerProvider('userUploads', (params, done) => {
// params: { page, perPage, searchText }
fetch(`/api/images?userId=123&page=${params.page}&perPage=${params.perPage}`)
.then((r) => r.json())
.then((data) => {
done(
data.items.map((img) => ({
id: img.id, // Required
location: img.url, // Required — the image URL
width: img.width, // Optional but recommended
height: img.height, // Optional but recommended
contentType: img.contentType, // Optional: 'image/png'
source: 'user', // Required: must be 'user'
})),
{ hasMore: data.hasMore, page: params.page, total: data.total }
);
});
});Your backend should return:
json
{
"items": [
{ "id": "img_1", "url": "https://...", "width": 800, "height": 600, "contentType": "image/png" }
],
"hasMore": true,
"total": 42
}See references/file-storage.md for upload progress with XHR, image deletion, and Amazon S3 setup.
需要在init中传入——图片按用户范围划分:
user.idjavascript
unlayer.init({
user: { id: 123 }, // 文件管理器必需
features: { userUploads: { enabled: true, search: true } },
});
unlayer.registerProvider('userUploads', (params, done) => {
// params: { page, perPage, searchText }
fetch(`/api/images?userId=123&page=${params.page}&perPage=${params.perPage}`)
.then((r) => r.json())
.then((data) => {
done(
data.items.map((img) => ({
id: img.id, // 必需
location: img.url, // 必需——图片URL
width: img.width, // 可选但推荐
height: img.height, // 可选但推荐
contentType: img.contentType, // 可选: 'image/png'
source: 'user', // 必需: 必须为'user'
})),
{ hasMore: data.hasMore, page: params.page, total: data.total }
);
});
});你的后端应返回:
json
{
"items": [
{ "id": "img_1", "url": "https://...", "width": 800, "height": 600, "contentType": "image/png" }
],
"hasMore": true,
"total": 42
}查看references/file-storage.md获取带XHR的上传进度、图片删除和Amazon S3设置的示例。
Localization
本地化
javascript
unlayer.init({
locale: 'es-ES',
textDirection: 'rtl', // 'ltr' | 'rtl' | null
translations: {
es: { Save: 'Guardar', Cancel: 'Cancelar' },
},
});javascript
unlayer.init({
locale: 'es-ES',
textDirection: 'rtl', // 'ltr' | 'rtl' | null
translations: {
es: { Save: 'Guardar', Cancel: 'Cancelar' },
},
});Validation
验证
javascript
// Global validator — runs on all content
unlayer.setValidator(async ({ html, design, defaultErrors }) => {
return [...defaultErrors]; // Return modified error list
});
// Per-tool validator
unlayer.setToolValidator('text', async ({ html, defaultErrors }) => {
return defaultErrors;
});
// Run audit on demand
unlayer.audit((result) => {
// result: { status: 'FAIL' | 'PASS', errors: [{ id, icon, severity, title, description }] }
if (result.status === 'FAIL') {
console.log('Issues found:', result.errors);
}
});javascript
// 全局验证器——对所有内容生效
unlayer.setValidator(async ({ html, design, defaultErrors }) => {
return [...defaultErrors]; // 返回修改后的错误列表
});
// 按工具验证
unlayer.setToolValidator('text', async ({ html, defaultErrors }) => {
return defaultErrors;
});
// 按需运行审核
unlayer.audit((result) => {
// result: { status: 'FAIL' | 'PASS', errors: [{ id, icon, severity, title, description }] }
if (result.status === 'FAIL') {
console.log('发现问题:', result.errors);
}
});safeHtml (XSS Protection)
safeHtml(XSS防护)
javascript
unlayer.init({
safeHtml: true, // Sanitize HTML via DOMPurify
// Or with custom options:
safeHtml: {
domPurifyOptions: {
FORCE_BODY: true,
},
},
// WRONG: safeHTML (capital HTML) is DEPRECATED — use safeHtml
});javascript
unlayer.init({
safeHtml: true, // 通过DOMPurify清理HTML
// 或使用自定义选项:
safeHtml: {
domPurifyOptions: {
FORCE_BODY: true,
},
},
// 错误写法: safeHTML(大写HTML)已被弃用——请使用safeHtml
});Common Mistakes
常见错误
| Mistake | Fix |
|---|---|
| Use |
| It disables blocks but NOT the tab — use |
Deprecated | Use |
Missing | File Manager requires |
| Project Secret exposed in frontend | Never put the secret in client code — generate HMAC server-side |
| Merge tag syntax mismatch | Match your template engine: |
| 错误 | 修复方案 |
|---|---|
| 使用 |
| 它会禁用块但不会隐藏标签页——请使用 |
已弃用的 | 改用 |
文件管理器缺少 | 文件管理器需要在init中传入 |
| 项目密钥暴露在前端 | 绝对不要在客户端代码中放入密钥——在服务端生成HMAC |
| Merge Tags语法不匹配 | 匹配你的模板引擎: |
Troubleshooting
故障排除
| Problem | Fix |
|---|---|
| Merge tags don't appear | Check |
| HMAC signature rejected | Ensure |
| File manager shows empty | Check |
| Theme doesn't apply | Use |
| 问题 | 修复方案 |
|---|---|
| Merge Tags不显示 | 确保 |
| HMAC签名被拒绝 | 确保 |
| 文件管理器显示为空 | 检查 |
| 主题未生效 | 使用 |
Paid Features
付费功能
| Feature | How to Enable |
|---|---|
| Custom CSS/JS | |
| Display conditions | |
| Style guide | |
| Export Image/PDF/ZIP | Cloud API key required |
| AI features | |
| Collaboration | |
| 功能 | 启用方式 |
|---|---|
| 自定义CSS/JS | 在init中传入 |
| 显示条件 | 使用 |
| 样式指南 | 使用 |
| 导出图片/PDF/ZIP | 需要云API密钥 |
| AI功能 | 设置 |
| 协作功能 | 设置 |