unlayer-config

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Configure the Editor

配置Unlayer编辑器

Overview

概述

Unlayer's behavior is controlled through
unlayer.init()
options and runtime methods. This skill covers features, appearance, dynamic content, security, and file storage.
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

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.,
{{first_name}}
). They use your template engine's syntax (Handlebars, Liquid, Jinja, etc.):
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是发送时会被替换的占位符(例如
{{first_name}}
)。它们使用你的模板引擎语法(Handlebars、Liquid、Jinja等):
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
user.id
in init — images are scoped per user:
javascript
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.id
——图片按用户范围划分:
javascript
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

常见错误

MistakeFix
safeHTML
(uppercase)
Use
safeHtml
(camelCase) — old casing deprecated
features.blocks = false
hides tab
It disables blocks but NOT the tab — use
tabs
config
Deprecated
colorPicker.presets
Use
colorPicker.colors
instead (string[] or ColorGroup[])
Missing
user.id
for file manager
File Manager requires
user.id
in init
Project Secret exposed in frontendNever put the secret in client code — generate HMAC server-side
Merge tag syntax mismatchMatch your template engine:
{{var}}
(Handlebars),
${var}
(JS),
{% %}
(Jinja)
错误修复方案
safeHTML
(大写)
使用
safeHtml
(小驼峰)——旧写法已被弃用
features.blocks = false
隐藏标签页
它会禁用块但不会隐藏标签页——请使用
tabs
配置
已弃用的
colorPicker.presets
改用
colorPicker.colors
(字符串数组或ColorGroup数组)
文件管理器缺少
user.id
文件管理器需要在init中传入
user.id
项目密钥暴露在前端绝对不要在客户端代码中放入密钥——在服务端生成HMAC
Merge Tags语法不匹配匹配你的模板引擎:
{{var}}
(Handlebars)、
${var}
(JS)、
{% %}
(Jinja)

Troubleshooting

故障排除

ProblemFix
Merge tags don't appearCheck
setMergeTags()
is called after
editor:ready
or passed in
init()
HMAC signature rejectedEnsure
user.id
matches exactly what you signed, and secret is correct
File manager shows emptyCheck
user.id
is set,
userUploads.enabled = true
, provider returns correct format
Theme doesn't applyUse
unlayer.setAppearance({ theme: 'modern_dark' })
or
unlayer.setTheme('modern_dark')
after init
问题修复方案
Merge Tags不显示确保
setMergeTags()
editor:ready
之后调用,或在
init()
中传入
HMAC签名被拒绝确保
user.id
与你签名的ID完全一致,且密钥正确
文件管理器显示为空检查
user.id
已设置、
userUploads.enabled = true
,且提供者返回正确格式
主题未生效使用
unlayer.setAppearance({ theme: 'modern_dark' })
unlayer.setTheme('modern_dark')
在init之后调用

Paid Features

付费功能

FeatureHow to Enable
Custom CSS/JS
customCSS
,
customJS
in init
Display conditions
setDisplayConditions()
Style guide
setStyleGuide()
Export Image/PDF/ZIPCloud API key required
AI features
features.ai
Collaboration
features.collaboration
功能启用方式
自定义CSS/JS在init中传入
customCSS
customJS
显示条件使用
setDisplayConditions()
样式指南使用
setStyleGuide()
导出图片/PDF/ZIP需要云API密钥
AI功能设置
features.ai
为true
协作功能设置
features.collaboration
为true

Resources

资源