unlayer-integration

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Integrate Unlayer Editor

集成Unlayer编辑器

Overview

概述

Unlayer provides official wrappers for React, Vue, and Angular, plus a plain JavaScript embed. All wrappers share the same underlying API — only the editor access pattern differs.
Unlayer为React、Vue和Angular提供了官方封装,同时也支持原生JavaScript嵌入。所有封装都基于相同的底层API——仅编辑器的访问方式有所不同。

Which Framework?

选择对应框架?

FrameworkPackageInstallEditor Access
React
react-email-editor
npm i react-email-editor
useRef<EditorRef>
ref.current?.editor
Vue
vue-email-editor
npm i vue-email-editor
this.$refs.emailEditor.editor
Angular
angular-email-editor
npm i angular-email-editor
@ViewChild
this.emailEditor.editor
Plain JSCDN script tag
<script>
embed
Global
unlayer
object
⚠️ Before installing any Unlayer package, verify the version exists on npm:
bash
npm view react-email-editor version   # check latest published version
Never pin a version number you haven't verified. Use
npm install <package> --save
without a version to get the latest, or run
npm view <package> versions --json
to see all available versions.

框架包名安装命令编辑器访问方式
React
react-email-editor
npm i react-email-editor
useRef<EditorRef>
ref.current?.editor
Vue
vue-email-editor
npm i vue-email-editor
this.$refs.emailEditor.editor
Angular
angular-email-editor
npm i angular-email-editor
@ViewChild
this.emailEditor.editor
原生JSCDN脚本标签
<script>
嵌入
全局
unlayer
对象
⚠️ 在安装任何Unlayer包之前,请先验证该版本是否在npm上存在:
bash
npm view react-email-editor version   # 查看最新发布版本
切勿锁定未验证的版本号。使用
npm install <package> --save
且不指定版本即可获取最新版,或运行
npm view <package> versions --json
查看所有可用版本。

React (Complete Working Example)

React(完整可用示例)

bash
npm install react-email-editor --save
tsx
import React, { useRef, useState } from 'react';
import EmailEditor, { EditorRef, EmailEditorProps } from 'react-email-editor';

const EmailBuilder = () => {
  const emailEditorRef = useRef<EditorRef>(null);
  const [saving, setSaving] = useState(false);

  // Save design JSON + export HTML to your backend
  const handleSave = () => {
    const unlayer = emailEditorRef.current?.editor;
    if (!unlayer) return;

    setSaving(true);
    unlayer.exportHtml(async (data) => {
      try {
        const response = await fetch('/api/templates', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            design: data.design,  // Save this — needed to edit later
            html: data.html,      // The rendered HTML output
          }),
        });
        if (!response.ok) throw new Error('Save failed');
        console.log('Saved successfully');
      } catch (err) {
        console.error('Save error:', err);
      } finally {
        setSaving(false);
      }
    });
  };

  // Load a saved design when editor is ready
  const onReady: EmailEditorProps['onReady'] = async (unlayer) => {
    try {
      const response = await fetch('/api/templates/123');
      if (response.ok) {
        const saved = await response.json();
        unlayer.loadDesign(saved.design); // Pass the saved design JSON
      }
    } catch (err) {
      console.log('No saved design, starting blank');
    }
  };

  return (
    <div>
      <button onClick={handleSave} disabled={saving}>
        {saving ? 'Saving...' : 'Save'}
      </button>
      <EmailEditor
        ref={emailEditorRef}
        onReady={onReady}
        options={{
          projectId: 123456, // Dashboard > Project > Settings
          displayMode: 'email',
        }}
      />
    </div>
  );
};

export default EmailBuilder;
Your backend should accept and return:
typescript
// POST /api/templates — save
{ design: object, html: string }

// GET /api/templates/:id — load
{ design: object, html: string, updatedAt: string }
React Props:
PropTypeDefaultDescription
options
Object
{}
All
unlayer.init()
options (projectId, displayMode, etc.)
tools
Object
{}
Per-tool configuration
appearance
Object
{}
Theme and panel settings
onReady
Function
Called when editor is ready (receives
unlayer
instance)
onLoad
Function
Called when iframe loads (before ready)
style
Object
{}
Container inline styles
minHeight
String
'500px'
Minimum editor height

bash
npm install react-email-editor --save
tsx
import React, { useRef, useState } from 'react';
import EmailEditor, { EditorRef, EmailEditorProps } from 'react-email-editor';

const EmailBuilder = () => {
  const emailEditorRef = useRef<EditorRef>(null);
  const [saving, setSaving] = useState(false);

  // 保存设计JSON并将HTML导出到后端
  const handleSave = () => {
    const unlayer = emailEditorRef.current?.editor;
    if (!unlayer) return;

    setSaving(true);
    unlayer.exportHtml(async (data) => {
      try {
        const response = await fetch('/api/templates', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            design: data.design,  // 保存该数据——后续编辑需要用到
            html: data.html,      // 渲染后的HTML输出
          }),
        });
        if (!response.ok) throw new Error('保存失败');
        console.log('保存成功');
      } catch (err) {
        console.error('保存错误:', err);
      } finally {
        setSaving(false);
      }
    });
  };

  // 编辑器就绪时加载已保存的设计
  const onReady: EmailEditorProps['onReady'] = async (unlayer) => {
    try {
      const response = await fetch('/api/templates/123');
      if (response.ok) {
        const saved = await response.json();
        unlayer.loadDesign(saved.design); // 传入已保存的设计JSON
      }
    } catch (err) {
      console.log('无已保存设计,从零开始');
    }
  };

  return (
    <div>
      <button onClick={handleSave} disabled={saving}>
        {saving ? '保存中...' : '保存'}
      </button>
      <EmailEditor
        ref={emailEditorRef}
        onReady={onReady}
        options={{
          projectId: 123456, // 控制台 > 项目 > 设置
          displayMode: 'email',
        }}
      />
    </div>
  );
};

export default EmailBuilder;
你的后端应支持接收和返回以下格式:
typescript
// POST /api/templates — 保存
{ design: object, html: string }

// GET /api/templates/:id — 加载
{ design: object, html: string, updatedAt: string }
React属性:
属性类型默认值说明
options
Object
{}
所有
unlayer.init()
选项(projectId、displayMode等)
tools
Object
{}
工具级配置
appearance
Object
{}
主题和面板设置
onReady
Function
编辑器就绪时调用(接收
unlayer
实例)
onLoad
Function
iframe加载完成时调用(在就绪之前)
style
Object
{}
容器内联样式
minHeight
String
'500px'
编辑器最小高度

Vue (Complete Working Example)

Vue(完整可用示例)

bash
npm install vue-email-editor --save
vue
<template>
  <div id="app">
    <button v-on:click="handleSave" :disabled="saving">
      {{ saving ? 'Saving...' : 'Save' }}
    </button>
    <EmailEditor ref="emailEditor" v-on:load="editorLoaded" />
  </div>
</template>

<script>
import { EmailEditor } from 'vue-email-editor';

export default {
  components: { EmailEditor },
  data() {
    return { saving: false };
  },
  methods: {
    async editorLoaded() {
      try {
        const response = await fetch('/api/templates/123');
        if (response.ok) {
          const saved = await response.json();
          this.$refs.emailEditor.editor.loadDesign(saved.design);
        }
      } catch (err) {
        console.log('No saved design, starting blank');
      }
    },
    handleSave() {
      this.saving = true;
      this.$refs.emailEditor.editor.exportHtml(async (data) => {
        try {
          await fetch('/api/templates', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ design: data.design, html: data.html }),
          });
        } catch (err) {
          console.error('Save error:', err);
        } finally {
          this.saving = false;
        }
      });
    },
  },
};
</script>
Props:
minHeight
,
options
,
tools
,
appearance
,
locale
,
projectId
.

bash
npm install vue-email-editor --save
vue
<template>
  <div id="app">
    <button v-on:click="handleSave" :disabled="saving">
      {{ saving ? '保存中...' : '保存' }}
    </button>
    <EmailEditor ref="emailEditor" v-on:load="editorLoaded" />
  </div>
</template>

<script>
import { EmailEditor } from 'vue-email-editor';

export default {
  components: { EmailEditor },
  data() {
    return { saving: false };
  },
  methods: {
    async editorLoaded() {
      try {
        const response = await fetch('/api/templates/123');
        if (response.ok) {
          const saved = await response.json();
          this.$refs.emailEditor.editor.loadDesign(saved.design);
        }
      } catch (err) {
        console.log('无已保存设计,从零开始');
      }
    },
    handleSave() {
      this.saving = true;
      this.$refs.emailEditor.editor.exportHtml(async (data) => {
        try {
          await fetch('/api/templates', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ design: data.design, html: data.html }),
          });
        } catch (err) {
          console.error('保存错误:', err);
        } finally {
          this.saving = false;
        }
      });
    },
  },
};
</script>
属性:
minHeight
options
tools
appearance
locale
projectId

Angular (Complete Working Example)

Angular(完整可用示例)

bash
npm install angular-email-editor --save
Module (app.module.ts):
typescript
import { EmailEditorModule } from 'angular-email-editor';

@NgModule({ imports: [EmailEditorModule] })
export class AppModule {}
Component (app.component.ts):
typescript
import { Component, ViewChild } from '@angular/core';
import { EmailEditorComponent } from 'angular-email-editor';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
})
export class AppComponent {
  @ViewChild(EmailEditorComponent)
  private emailEditor: EmailEditorComponent;
  saving = false;

  async editorLoaded() {
    try {
      const response = await fetch('/api/templates/123');
      if (response.ok) {
        const saved = await response.json();
        this.emailEditor.editor.loadDesign(saved.design);
      }
    } catch (err) {
      console.log('No saved design');
    }
  }

  handleSave() {
    this.saving = true;
    this.emailEditor.editor.exportHtml(async (data) => {
      try {
        await fetch('/api/templates', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ design: data.design, html: data.html }),
        });
      } catch (err) {
        console.error('Save error:', err);
      } finally {
        this.saving = false;
      }
    });
  }
}
Template (app.component.html):
html
<div>
  <button (click)="handleSave()" [disabled]="saving">
    {{ saving ? 'Saving...' : 'Save' }}
  </button>
  <email-editor (loaded)="editorLoaded($event)"></email-editor>
</div>

bash
npm install angular-email-editor --save
模块(app.module.ts):
typescript
import { EmailEditorModule } from 'angular-email-editor';

@NgModule({ imports: [EmailEditorModule] })
export class AppModule {}
组件(app.component.ts):
typescript
import { Component, ViewChild } from '@angular/core';
import { EmailEditorComponent } from 'angular-email-editor';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
})
export class AppComponent {
  @ViewChild(EmailEditorComponent)
  private emailEditor: EmailEditorComponent;
  saving = false;

  async editorLoaded() {
    try {
      const response = await fetch('/api/templates/123');
      if (response.ok) {
        const saved = await response.json();
        this.emailEditor.editor.loadDesign(saved.design);
      }
    } catch (err) {
      console.log('无已保存设计');
    }
  }

  handleSave() {
    this.saving = true;
    this.emailEditor.editor.exportHtml(async (data) => {
      try {
        await fetch('/api/templates', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ design: data.design, html: data.html }),
        });
      } catch (err) {
        console.error('保存错误:', err);
      } finally {
        this.saving = false;
      }
    });
  }
}
模板(app.component.html):
html
<div>
  <button (click)="handleSave()" [disabled]="saving">
    {{ saving ? '保存中...' : '保存' }}
  </button>
  <email-editor (loaded)="editorLoaded($event)"></email-editor>
</div>

Plain JavaScript

原生JavaScript

html
<script src="https://editor.unlayer.com/embed.js"></script>
<div id="editor-container" style="height: 700px;"></div>

<script>
  unlayer.init({
    id: 'editor-container',
    projectId: 1234,
    displayMode: 'email',
  });

  unlayer.addEventListener('editor:ready', async function () {
    try {
      const response = await fetch('/api/templates/123');
      if (response.ok) {
        const saved = await response.json();
        unlayer.loadDesign(saved.design);
      }
    } catch (err) {
      console.log('Starting blank');
    }
  });

  function handleSave() {
    unlayer.exportHtml(async function (data) {
      await fetch('/api/templates', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ design: data.design, html: data.html }),
      });
    });
  }
</script>

html
<script src="https://editor.unlayer.com/embed.js"></script>
<div id="editor-container" style="height: 700px;"></div>

<script>
  unlayer.init({
    id: 'editor-container',
    projectId: 1234,
    displayMode: 'email',
  });

  unlayer.addEventListener('editor:ready', async function () {
    try {
      const response = await fetch('/api/templates/123');
      if (response.ok) {
        const saved = await response.json();
        unlayer.loadDesign(saved.design);
      }
    } catch (err) {
      console.log('从零开始');
    }
  });

  function handleSave() {
    unlayer.exportHtml(async function (data) {
      await fetch('/api/templates', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ design: data.design, html: data.html }),
      });
    });
  }
</script>

Multiple Editor Instances

多编辑器实例

Use
createEditor()
instead of
init()
:
javascript
const editor1 = unlayer.createEditor({ id: 'email-editor', displayMode: 'email' });
const editor2 = unlayer.createEditor({ id: 'web-editor', displayMode: 'web' });

editor1.loadDesign(emailDesign);
editor2.loadDesign(webDesign);

使用
createEditor()
替代
init()
javascript
const editor1 = unlayer.createEditor({ id: 'email-editor', displayMode: 'email' });
const editor2 = unlayer.createEditor({ id: 'web-editor', displayMode: 'web' });

editor1.loadDesign(emailDesign);
editor2.loadDesign(webDesign);

Common Mistakes

常见错误

MistakeFix
Container too smallMinimum 1024px wide x 700px tall. Editor fills its container.
Calling methods before readyAlways wait for
editor:ready
event or
onReady
callback
Using
init()
for multiple editors
Use
unlayer.createEditor()
instead
Missing
projectId
Get it from Dashboard > Project > Settings
Only saving HTML, not design JSONAlways save both — design JSON lets users edit later
No loading state while savingDisable the save button to prevent double saves
Pinning a non-existent package versionRun
npm view <package> version
to verify before pinning. Use
npm install <package> --save
without a version to get the latest.
错误修复方案
容器尺寸过小最小尺寸为1024px宽 x 700px高。编辑器会填充其所在容器。
在就绪前调用方法始终等待
editor:ready
事件或
onReady
回调
使用
init()
创建多编辑器
改用
unlayer.createEditor()
缺少
projectId
从控制台 > 项目 > 设置中获取
仅保存HTML,未保存设计JSON务必同时保存两者——设计JSON支持后续编辑
保存时无加载状态禁用保存按钮以防止重复保存
锁定不存在的包版本锁定前先运行
npm view <package> version
验证。使用
npm install <package> --save
且不指定版本即可获取最新版。

Troubleshooting

故障排除

ErrorCauseFix
Editor shows blank white spaceContainer div has 0 heightSet explicit height:
min-height: 700px
editor:ready
never fires
Script not loaded or wrong
id
Check
id
matches an existing div, check network tab for embed.js
ref.current?.editor
is undefined
Accessed before mountOnly use inside
onReady
callback
Design doesn't loadMalformed JSONValidate JSON, check
schemaVersion
field
错误原因修复方案
编辑器显示空白容器div高度为0设置明确高度:
min-height: 700px
editor:ready
事件从未触发
脚本未加载或
id
错误
检查
id
是否与现有div匹配,检查网络面板中的embed.js加载情况
ref.current?.editor
为undefined
在挂载前访问仅在
onReady
回调中使用
设计无法加载JSON格式错误验证JSON,检查
schemaVersion
字段

Resources

资源