English
Migration from Unlayer
The @templatical/import-unlayer package converts Unlayer design JSON (the output of editor.saveDesign(...) from react-email-editor or the Unlayer hosted editor) into Templatical's TemplateContent format.
WARNING
This package is in active development. Some content types and advanced features may not be fully supported yet. Test your converted templates before using them in production.
Installation
bash
npm install @templatical/import-unlayerbash
pnpm add @templatical/import-unlayerbash
yarn add @templatical/import-unlayerbash
bun add @templatical/import-unlayerWithout a build step (CDN)
You can also load it from a CDN:
html
<script type="module">
import { convertUnlayerTemplate } from 'https://cdn.jsdelivr.net/npm/@templatical/import-unlayer/+esm';
// ...then convert as shown in Usage below
</script>Usage
ts
import { convertUnlayerTemplate } from '@templatical/import-unlayer';
// Load your Unlayer design JSON (whatever editor.saveDesign returned)
const res = await fetch('/api/unlayer-templates/123');
const unlayerJson = await res.json();
// Convert to Templatical format
const { content, report } = convertUnlayerTemplate(unlayerJson);
// Use in the editor
const editor = await init({
container: '#editor',
content,
});
// Check the conversion report for any issues
console.log(report);Each report.entries item is one source module:
| Status | Meaning |
|---|---|
converted | Mapped to a Templatical block with no loss of fidelity. |
approximated | Mapped, with a clamp or flatten — note states what changed. |
html-fallback | No block equivalent; original markup is an HtmlBlock. |
skipped | No output (forms, and anything the converter refuses). |
The design JSON Unlayer's hosted editor and editor.saveDesign() emit is the input. A compiled HTML export is a different envelope — use @templatical/import-html.
The function returns an ImportResult with:
content— the convertedTemplateContentready for the editorreport— a conversion report with the status of each content node (converted,approximated,html-fallback, orskipped)
Block Mapping
Unlayer content types map to Templatical equivalents:
| Unlayer Content | Templatical Block | Status |
|---|---|---|
| Text | paragraph | Converted |
| Heading | title | Converted |
| Image | image | Converted |
| Button | button | Converted |
| Divider | divider | Converted (approximated when a partial-width divider is aligned left or right) |
| Spacer | spacer | Converted |
| Html | html | Converted |
| Menu | menu | Approximated (styles may differ) |
| Social | social | Converted |
| Video | video | Converted |
| Timer | html | HTML fallback (rebuild manually) |
| Form | — | Skipped |
Unknown content types are converted to HTML blocks as a fallback.
Divider width
Unlayer width | DividerBlock.width | Status |
|---|---|---|
missing, or 100% | "full" | Converted |
a percentage under 100%, such as 50% | the same percentage to two decimals, "50%" | Converted |
below 0% or above 100% | clamped to "0%" or "full" | Approximated |
| px, narrower than the line's span | the px number | Converted |
| px, as wide as that span or wider | "full" | Converted |
| any other value | "full" | Approximated |
A px width is compared with the line's span: its column's width less the divider's left and right containerPadding. The column's width is its share of settings.width under the section's column layout, and settings.width is a px contentWidth, else 600.
TIP
mj-divider draws 100% across that span, so a px width that reaches it renders the same as "full". "full" also narrows with the column on a phone.
Templatical centres every divider. A partial-width divider that Unlayer aligns left or right (textAlign) is approximated, and its note names the alignment.
Column Layout Conversion
Unlayer organizes content into rows with columns whose widths come from a cells weight array. These map to Templatical's SectionBlock with the appropriate ColumnLayout:
| Unlayer cells | Templatical Layout |
|---|---|
[1] (single column) | '1' |
[1, 1] (equal halves) | '2' |
[1, 1, 1] (equal thirds) | '3' |
[1, 2] | '1-2' |
[2, 1] | '2-1' |
| 4+ cells | flattened to single column with a warning |
Cell ratios that don't match a standard layout are mapped to the closest available one.
Section background
| Unlayer row values | section.styles.backgroundColor |
|---|---|
columnsBackgroundColor set | columnsBackgroundColor |
only backgroundColor set | backgroundColor |
When a row sets both to different colours, the section takes columnsBackgroundColor and report.warnings names the dropped backgroundColor.
TIP
columnsBackgroundColor fills the content width, the area a section paints. backgroundColor fills the band outside it, and a Templatical section has no full-width band.
Template Settings
Global template settings are converted where possible:
- Width — Unlayer
body.values.contentWidthmaps tosettings.width - Background color —
body.values.backgroundColormaps tosettings.backgroundColor - Text color —
body.values.textColormaps tosettings.textColor. Headings, menus and paragraphs with no colour of their own inherit it. - Links —
body.values.linkStyle.linkColormaps tosettings.linkColor, andlinkStyle.linkUnderlinetosettings.linkUnderline. WhenlinkStylesets nolinkUnderline, links are underlined, as in Unlayer. - Preheader —
body.values.preheaderTextmaps tosettings.preheaderText, omitted when empty - Font family —
body.values.fontFamily.valuecarries over tosettings.fontFamily
Known Limitations
- Custom fonts — Unlayer custom font declarations are not automatically imported. Add them manually via the
fontsconfig option. - Display conditions / dynamic content — Unlayer's conditional content syntax has no direct equivalent and is dropped during conversion. Use Templatical's display conditions to recreate them.
- Custom modules / paid-tier blocks — Unlayer custom blocks are converted to placeholder HTML blocks. Recreate them as a custom block if reusable.
- Forms — Unlayer form blocks are skipped. Most email clients block form submission for security reasons; rebuild the call-to-action as a button linking to a hosted form.
- Timers / countdowns — Imported as a placeholder HTML block. Do not emit
type: "countdown": that block needs Cloud's server-side GIF and the OSS renderer cannot produce it. Recreate as a static title or paragraph (the date, or "X days to go"), or keep the HTML placeholder. - Link hover styles and per-block link styles —
linkStyle.linkHoverColorandlinkHoverUnderlinehave no Templatical field and are dropped. A text block's ownlinkStyleis not read, so its links take the document's link settings. - AMP for Email — not currently supported in Templatical.
Verifying Converted Templates
After conversion, review the output in the editor to check for:
- Missing images (re-upload or update URLs if needed)
- Font rendering (add custom fonts to the editor config)
- Column proportions (adjust layouts if the automatic mapping doesn't match)
- Spacing and padding (fine-tune in the block settings panel)