---
title: "Unlayer compatibility, MailKaro Docs"
description: "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."
---

# 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.

diff EmailStudio.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.loadDesign editor.loadBlank editor.saveDesign editor.exportHtml editor.exportLiveHtml editor.exportPlainText editor.exportAmp editor.exportDocument editor.exportZip editor.setMergeTags editor.setBodyValues editor.showPreview editor.hidePreview editor.registerCallback editor.unregisterCallback editor.addEventListener editor.removeEventListener editor.undo editor.redo editor.canUndo editor.canRedo editor.audit editor.setValidator editor.setLocale editor.setDisplayMode editor.setAppearance editor.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](https://mailkaro.com/docs/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](https://mailkaro.com/docs/configuration) and [embed token](https://mailkaro.com/docs/embed-tokens).

New to the API? See the [Editor component](https://mailkaro.com/docs/editor) reference.

Source: https://docs.mailkaro.com/migrate
