Unlayer compatibility

Getting started

Unlayer-compatible data

react-email-craft exposes the same component, props, and ref API as react-email-editor, and reads its design JSON, so your existing designs load and render with no rework.

diffEmailStudio.tsx
- import EmailEditor, { type EditorRef, type EmailEditorProps } from "react-email-editor";
+ import EmailEditor, { type EditorRef, type EmailEditorProps } from "react-email-craft";

  <EmailEditor ref={emailEditorRef} onLoad={onLoad} onReady={onReady} options={{ mergeTags }} />

The other frameworks swap the same way (vanilla JS also renames its global):

  • Vue: vue-email-editor → vue-email-craft (import { EmailEditor }).
  • Angular: angular-email-editor → angular-email-craft.
  • Vanilla JS: replace Unlayer's embed.js script tag with https://unpkg.com/vanilla-email-craft/dist/embed.js; it defines the mailkaro global, so rename your calls (mailkaro.init, mailkaro.createEditor).

What converts automatically#

  • Designs. When you call loadDesign(), existing Unlayer design JSON is detected and converted to MailKaro's format, rows, columns, and column widths, plus these tools mapped to their MailKaro blocks: text, heading, image, button, divider, menu, social, html, spacer, table, timer, and video. Any other tool falls back to a raw-HTML or placeholder block so nothing is lost.
  • Merge tags. Unlayer's grouped merge-tag arrays/objects passed via options.mergeTags are flattened to MailKaro's format automatically. Nested groups become Group · Tag labels. Tags inside text render as editable pills.
How storage works. Existing Unlayer design JSON loads and renders as-is. The moment a user edits and saves, saveDesign() returns MailKaro's native schema, so from then on the design is stored in MailKaro's own format (a one-way migration: Unlayer JSON in, MailKaro JSON out, with the same output). Designs not re-saved yet still render on your server: renderDesign, renderMjml and the other email-craft-sdk render functions take Unlayer JSON as is, and unlayerDesignToMailkaro converts one explicitly.

Ref API parity#

These Unlayer calls work unchanged:

editor.loadDesigneditor.loadBlankeditor.saveDesigneditor.exportHtmleditor.exportLiveHtmleditor.exportPlainTexteditor.exportAmpeditor.exportDocumenteditor.exportZipeditor.setMergeTagseditor.setBodyValueseditor.showPrevieweditor.hidePrevieweditor.registerCallbackeditor.unregisterCallbackeditor.addEventListenereditor.removeEventListenereditor.undoeditor.redoeditor.canUndoeditor.canRedoeditor.auditeditor.setValidatoreditor.setLocaleeditor.setDisplayModeeditor.setAppearanceeditor.setTextDirection

Calls made before the editor is ready (from onLoad, or right after creating it) are queued and run once it is. exportHtml also returns Unlayer's chunks (body, css) and takes its mergeTags option. An image callback gets the file with Unlayer's attachments, and may report progress with done({ progress }) before done({ progress: 100, url }). exportImage and exportPdf answer with Unlayer's no-export shape (url: null); render those on your server with email-craft-sdk. registerTool and createViewer add your own blocks (see Custom tools). Other Unlayer methods (setDesignId, registerProvider, ...) are accepted and have no effect.

Unlayer's displayConditions option and setDisplayConditions work: a row's settings get a Display condition picker, and the chosen condition's before and after go around the row in the exported HTML (and MJML), so your sending tool decides who sees it. Designs saved by Unlayer keep the conditions on their rows and blocks. A preview rendered with merge data shows the content without them. Unlayer's features.textEditor.customButtons work too: each button shows in the text toolbar, and its onAction({ text }, done) runs on your page with the selected words; done(text) puts the result in their place.

ts
mailkaro.init({
  id: "editor",
  displayConditions: [
    { type: "Plan", label: "Pro customers", description: "Only the Pro plan", before: "{% if plan == 'pro' %}", after: "{% endif %}" }
  ],
  features: {
    textEditor: {
      customButtons: [{ name: "shout", text: "Shout", icon: "S", onAction: (data, done) => done(data.text.toUpperCase()) }]
    }
  }
});

Events and layout#

  • onLoad (Vue and Angular: load/loaded) fires when the editor is created, then onReady (ready) when it has loaded.
  • design:loaded fires with { design } for the opening design and after loadDesign/loadBlank; design:updated fires for edits (with Unlayer's change type), not for loading a design.
  • Like Unlayer, there is one listener per event name: adding another for the same name replaces it.
  • The component lays out like Unlayer's: React and Angular grow to fill a flex parent; Vue, like vue-email-editor, is minHeight tall unless you size it with style. minHeight defaults to 500px, editorId names the container and style applies to it.
Unlayer-only options (projectId, appearance, tools, scriptUrl) are accepted for parity but ignored. MailKaro is styled and configured through its own config and embed token.

New to the API? See the Editor component reference.