Skip to content

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-unlayer
bash
pnpm add @templatical/import-unlayer
bash
yarn add @templatical/import-unlayer
bash
bun add @templatical/import-unlayer

Without 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);

Open in playground

Each report.entries item is one source module:

StatusMeaning
convertedMapped to a Templatical block with no loss of fidelity.
approximatedMapped, with a clamp or flatten — note states what changed.
html-fallbackNo block equivalent; original markup is an HtmlBlock.
skippedNo 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 converted TemplateContent ready for the editor
  • report — a conversion report with the status of each content node (converted, approximated, html-fallback, or skipped)

Block Mapping ​

Unlayer content types map to Templatical equivalents:

Unlayer ContentTemplatical BlockStatus
TextparagraphConverted
HeadingtitleConverted
ImageimageConverted (approximated when a maxWidth under 100% becomes px; see Image width)
ButtonbuttonConverted
DividerdividerConverted (approximated when a partial-width divider is aligned left or right)
SpacerspacerConverted
HtmlhtmlConverted
MenumenuApproximated (styles may differ)
SocialsocialConverted
VideovideoConverted
TimerhtmlHTML fallback (rebuild manually)
Form—Skipped

Unknown content types are converted to HTML blocks as a fallback.

Image width ​

Unlayer imageImageBlock.widthStatus
autoWidth (the default), src.width narrower than the image's roomthe px numberConverted
autoWidth, src.width as wide as the room or wider, or missing"full"Converted
autoWidth: false, maxWidth 100% or missing"full"Converted
autoWidth: false, maxWidth under 100%, such as 75%that share of the room in pxApproximated
autoWidth: false, unreadable maxWidth"full"Approximated

src.width and src.height are the uploaded file's size. The image's room is its column's width less its left and right containerPadding. src.height is not imported, so the image keeps the file's aspect ratio at the width it is shown at.

TIP

A px width keeps its size on a phone, where a percentage shrinks with the column, so a maxWidth under 100% is approximated.

Divider width ​

Unlayer widthDividerBlock.widthStatus
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 spanthe px numberConverted
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 cellsTemplatical 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+ cellsflattened 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 valuessection.styles.backgroundColor
columnsBackgroundColor setcolumnsBackgroundColor
only backgroundColor setbackgroundColor

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.

Section background image ​

A row's backgroundImage becomes the section's backgroundImage:

backgroundImage.fullWidthResult
false (the content area)The section's image, exactly.
true, columnsBackgroundColor unsetThe section's image, painted at the content width, and report.warnings says so.
true, columnsBackgroundColor setDropped, since in Unlayer the content colour covers it inside the content width, and report.warnings names it.

size cover and contain carry over, and custom is the image at its own size (auto). The nine position keywords map to the matching grid point. A custom position's customPosition percentage snaps to the nearest of left, center and right (top, center and bottom), and a length to the left or top edge. repeat-x and repeat-y become repeat. Older designs' cover and center flags are read when size and position are absent: cover maps to cover (false to auto), and center to center top (false to left top). Each value with no exact keyword adds a line to report.warnings.

Template Settings ​

Global template settings are converted where possible:

  • Width — Unlayer body.values.contentWidth maps to settings.width
  • Background color — body.values.backgroundColor maps to settings.backgroundColor
  • Background image — body.values.backgroundImage has no Templatical equivalent, since the email body takes a colour only. It's dropped, and report.warnings names it.
  • Text color — body.values.textColor maps to settings.textColor. Headings, menus and paragraphs with no colour of their own inherit it.
  • Links — body.values.linkStyle.linkColor maps to settings.linkColor, and linkStyle.linkUnderline to settings.linkUnderline. When linkStyle sets no linkUnderline, links are underlined, as in Unlayer.
  • Preheader — body.values.preheaderText maps to settings.preheaderText, omitted when empty
  • Font family — body.values.fontFamily.value carries over to settings.fontFamily. The whole stack carries over, so its fallbacks survive; a stack that is exactly a built-in font's own (Arial, sans-serif) is stored as the bare name.

Known Limitations ​

  • Custom fonts — Unlayer custom font declarations are not automatically imported. Add them manually via the fonts config 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.linkHoverColor and linkHoverUnderline have no Templatical field and are dropped. A text block's own linkStyle is 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:

  1. Missing images (re-upload or update URLs if needed)
  2. Font rendering (add custom fonts to the editor config)
  3. Column proportions (adjust layouts if the automatic mapping doesn't match)
  4. Spacing and padding (fine-tune in the block settings panel)