Unlayer export
AI skills for integrating the Unlayer drag-and-drop editor. Install with: npx skills add unlayer/unlayer-skills
npx -y skills add unlayer/unlayer-skills --skill unlayer-exportAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 12 stars12 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Exports content from the Unlayer editor — HTML, PDF, image, plain text, saving and loading design JSON, auto-save patterns, server-side Cloud API export.
SKILL.md
8.1 KB, as published. Nobody here has run it
Export Content
Overview
Unlayer supports multiple export formats. Some are client-side (free), others use the Cloud API (paid).
Which Export Method?
| Method | Output | Paid? | Use When |
|---|---|---|---|
exportHtml | HTML + design JSON | No | Email sending, web publishing, saving designs |
exportPlainText | Plain text + design | No | SMS, accessibility fallback |
exportImage | PNG URL + design | Yes | Thumbnails, previews, social sharing |
exportPdf | PDF URL + design | Yes | Print-ready documents |
exportZip | ZIP URL + design | Yes | Offline download packages |
Critical: Always save the design JSON alongside any export. All export methods return
data.design— save it so users can edit later.
Save & Load Designs
// SAVE — use exportHtml to get both design JSON and HTML
unlayer.exportHtml(async (data) => {
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
}),
});
});
// LOAD — restore a saved design (must wait for editor:ready)
unlayer.addEventListener('editor:ready', async () => {
const response = await fetch('/api/templates/123');
const saved = await response.json();
unlayer.loadDesign(saved.design); // Pass the saved JSON object
});
// LOAD BLANK
unlayer.loadBlank({ backgroundColor: '#ffffff', contentWidth: '600px' });
// LOAD AN UNLAYER TEMPLATE
unlayer.loadTemplate(templateId); // ID from Unlayer dashboard
Export HTML
unlayer.exportHtml((data) => {
const { html, design, chunks } = data;
// html — Full HTML document (string)
// design — Design JSON (always save this!)
// chunks — { css, js, body, fonts, tags }
// body — Just the content inside <body> (no wrapper)
// css — Extracted CSS styles
// fonts — Web fonts used in the design
// Save both to your backend
await fetch('/api/templates', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ design, html }),
});
}, {
// All options are optional
cleanup: true, // Remove editor markup (default: true)
minify: false, // Minify HTML output
inlineStyles: false, // Move CSS inline (for email clients)
mergeTags: {}, // Replace merge tags with real values
title: 'My Email', // Set HTML <title>
});
Using chunks — when you need just the body content (no <!DOCTYPE> wrapper):
unlayer.exportHtml((data) => {
const { body, css, fonts } = data.chunks;
const myHtml = `<style>${css}</style>${fonts}${body}`;
});
Export Plain Text
unlayer.exportPlainText((data) => {
const { text, design } = data;
// Use as email plain-text fallback
}, {
ignorePreheader: false,
ignoreLinks: false,
ignoreImages: false,
mergeTags: {},
});
Export Image (Paid — Cloud API)
Generates a PNG screenshot of the design. The image uploads to your connected File Storage.
Client-side:
unlayer.exportImage((data) => {
// data.url — PNG URL
// data.design — Design JSON (always save this!)
console.log('Image URL:', data.url);
}, {
fullPage: false, // true = entire page, false = viewport
mergeTags: {},
});
Server-side via Cloud API (get API key from Dashboard > Project > Settings > API Keys):
const response = await fetch('https://api.unlayer.com/v2/export/image', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Basic ' + Buffer.from('YOUR_API_KEY:').toString('base64'),
},
body: JSON.stringify({
displayMode: 'email',
design: designJSON, // The saved design JSON object
mergeTags: {},
}),
});
const data = await response.json();
// data.url — image URL
Export PDF / ZIP (Paid — Cloud API)
// PDF
unlayer.exportPdf((data) => {
// data.url — PDF URL
// data.design — Design JSON
}, { mergeTags: {} });
// ZIP
unlayer.exportZip((data) => {
// data.url — ZIP URL
// data.design — Design JSON
}, { mergeTags: {} });
Auto-Save Pattern
Design + HTML (recommended):
let saveTimeout;
unlayer.addEventListener('design:updated', () => {
clearTimeout(saveTimeout);
saveTimeout = setTimeout(() => {
unlayer.exportHtml(async (data) => {
await fetch('/api/templates/123', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ design: data.design, html: data.html }),
});
});
}, 1000);
});
Design + HTML + Thumbnail (full):
let saveTimeout;
unlayer.addEventListener('design:updated', () => {
clearTimeout(saveTimeout);
saveTimeout = setTimeout(() => {
// Save design + HTML immediately
unlayer.exportHtml(async (data) => {
await fetch('/api/templates/123', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ design: data.design, html: data.html }),
});
});
// Generate thumbnail (slower, paid — debounce longer or do on manual save)
unlayer.exportImage(async (data) => {
if (!data.url) return;
await fetch('/api/templates/123/thumbnail', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ thumbnailUrl: data.url }),
});
}, { fullPage: false });
}, 3000); // Longer debounce for image generation
});
Design JSON Quick Reference
The design JSON has this structure (see references/design-json.md for full TypeScript types):
JSONTemplate
├── counters — Internal counters
├── schemaVersion — Schema version number
└── body
├── rows[] — Each row contains columns
│ ├── cells[] — Column ratios: [1,1] = 50/50
│ └── columns[]
│ └── contents[] — Content items (text, image, button...)
├── headers[] — Same as rows (with headersAndFooters feature)
├── footers[] — Same as rows
└── values — Body-level styles (backgroundColor, contentWidth, fontFamily)
Content types: text, heading, button, image, divider, social, html, video, menu, timer.
Common Mistakes
| Mistake | Fix |
|---|---|
| Only saving HTML, not design JSON | Always save both — all export methods return data.design |
Calling export before editor:ready | Wait for the event first |
| Not configuring File Storage for image/PDF export | Image and PDF uploads go to your connected File Storage |
| Not debouncing auto-save | design:updated fires on every keystroke — debounce 1-3 seconds |
Ignoring chunks in exportHtml | Use chunks.body when you need just content without <!DOCTYPE> wrapper |
| Missing API key for image/PDF/ZIP | Cloud API key required — get from Dashboard > Project > Settings > API Keys |
Troubleshooting
| Problem | Fix |
|---|---|
exportImage returns error | Check API key, check Cloud API plan, verify design isn't empty |
| Exported HTML looks different from editor | Use cleanup: true (default), check custom CSS |
design:updated fires too often | Always debounce — it fires on every property change |
| Loaded design shows blank | Check schemaVersion compatibility, validate JSON structure |