Get the FREE Ultimate OpenClaw Setup Guide →

unlayer-config

Scanned
npx machina-cli add skill unlayer/unlayer-skills/unlayer-config --openclaw
Files (1)
SKILL.md
11.6 KB

Configure the Editor

Overview

Unlayer's behavior is controlled through unlayer.init() options and runtime methods. This skill covers features, appearance, dynamic content, security, and file storage.

Where to find keys:

  • Project ID — Dashboard > Project > Settings
  • Project Secret (for HMAC) — Dashboard > Project > Settings > API Keys
  • Cloud API Key (for image/PDF export) — Dashboard > Project > Settings > API Keys

Dashboard: console.unlayer.com


Feature Flags

Control what's available in the editor:

unlayer.init({
  features: {
    audit: true,             // Content validation
    preview: true,           // Preview button
    undoRedo: true,          // Undo/redo
    stockImages: true,       // Stock photo library
    userUploads: true,       // User upload tab
    preheaderText: true,     // Email preheader field
    textEditor: {
      spellChecker: true,
      tables: false,         // Tables inside text blocks
      cleanPaste: 'confirm', // true | false | 'basic' | 'confirm'
      emojis: true,
    },

    // Paid features:
    ai: true,                // AI text generation
    collaboration: false,    // Real-time collaboration
    sendTestEmail: false,    // Test email button
  },
});

See references/feature-flags.md for all flags (AI sub-options, image editor, color picker, etc.).


Appearance & Theming

unlayer.init({
  appearance: {
    theme: 'modern_dark',    // 'modern_light' | 'modern_dark' | 'classic_light' | 'classic_dark'
    panels: {
      tools: {
        dock: 'right',       // 'left' | 'right' (default: 'right')
        collapsible: true,
      },
    },
    actionBar: {
      placement: 'top',     // 'top' | 'bottom' | 'top_left' | 'top_right' | 'bottom_left' | 'bottom_right'
    },
  },
});

// Change at runtime:
unlayer.setAppearance({ theme: 'modern_dark' });
// Or just the theme:
unlayer.setTheme('modern_dark');

Custom Fonts

unlayer.init({
  fonts: {
    showDefaultFonts: true,
    customFonts: [{
      label: 'Poppins',
      value: "'Poppins', sans-serif",
      url: 'https://fonts.googleapis.com/css?family=Poppins:400,700',
      weights: [400, 700],  // or [{ label: 'Regular', value: 400 }, { label: 'Bold', value: 700 }]
    }],
  },
});

Tab Configuration

unlayer.init({
  tabs: {
    content: { enabled: true, position: 1 },
    blocks: { enabled: true, position: 2 },
    body: { enabled: true, position: 3 },
    images: { enabled: true, position: 4 },
    uploads: { enabled: true, position: 5 },
  },
});

Merge Tags

Merge tags are placeholders replaced at send time (e.g., {{first_name}}). They use your template engine's syntax (Handlebars, Liquid, Jinja, etc.):

unlayer.setMergeTags({
  first_name: {
    name: 'First Name',
    value: '{{first_name}}',        // Your template syntax
    sample: 'John',                  // Shown in editor preview
  },
  last_name: {
    name: 'Last Name',
    value: '{{last_name}}',
    sample: 'Doe',
  },
  company: {
    name: 'Company',                 // Nested group
    mergeTags: {
      name: { name: 'Company Name', value: '{{company.name}}', sample: 'Acme Inc' },
      logo: { name: 'Logo URL', value: '{{company.logo}}' },
    },
  },
  products: {
    name: 'Products',
    rules: {
      repeat: {
        name: 'Repeat for Each Product',
        before: '{{#each products}}', // Loop start — syntax depends on your template engine
        after: '{{/each}}',           // Loop end
        sample: true,                  // Show sample data in editor
      },
    },
    mergeTags: {
      name: { name: 'Product Name', value: '{{this.name}}' },
      price: { name: 'Price', value: '{{this.price}}' },
      image: { name: 'Image URL', value: '{{this.image}}' },
    },
  },
});

// Autocomplete trigger (optional)
unlayer.setMergeTagsConfig({ autocompleteTriggerChar: '{{', sort: true });

Design Tags (Editor-Only Placeholders)

Design tags are replaced in the editor UI but NOT in exports — useful for showing personalized content to the template author:

unlayer.setDesignTags({
  business_name: 'Acme Corp',
  current_user_name: 'Jane Smith',
});
unlayer.setDesignTagsConfig({ delimiter: ['{{', '}}'] });

Display Conditions (Paid)

Wrap content in conditional blocks for your template engine:

unlayer.setDisplayConditions([
  {
    type: 'segment',
    label: 'VIP Customers',
    description: 'Only shown to VIP segment',
    before: '{% if customer.vip %}',   // Your template engine syntax
    after: '{% endif %}',
  },
  {
    type: 'segment',
    label: 'New Subscribers',
    description: 'First 30 days only',
    before: '{% if subscriber.age_days < 30 %}',
    after: '{% endif %}',
  },
]);

Special Links

Pre-defined links users can insert (unsubscribe, preferences, etc.):

unlayer.setSpecialLinks({
  unsubscribe: {
    name: 'Unsubscribe',
    href: '{{unsubscribe_url}}',
    target: '_blank',
  },
  preferences: {
    name: 'Preferences',
    specialLinks: {
      email_prefs: { name: 'Email Preferences', href: '{{preferences_url}}' },
      profile: { name: 'Profile Settings', href: '{{profile_url}}' },
    },
  },
});

HMAC Security

Prevents users from impersonating each other. Generate the HMAC signature server-side using your Project Secret (Dashboard > Project > Settings > API Keys):

Node.js:

const crypto = require('crypto');
const signature = crypto
  .createHmac('sha256', 'YOUR_PROJECT_SECRET')  // From Dashboard
  .update(String(userId))
  .digest('hex');

Python/Django:

import hmac, hashlib

signature = hmac.new(
    b'YOUR_PROJECT_SECRET',
    bytes(str(request.user.id), encoding='utf-8'),
    digestmod=hashlib.sha256
).hexdigest()

Client-side — pass the server-generated signature:

unlayer.init({
  user: {
    id: userId,                       // Must match what you signed
    signature: signatureFromServer,    // HMAC from your backend
    name: 'John Doe',                 // Optional
    email: 'john@acme.com',          // Optional
  },
});

See references/security.md for Ruby and PHP examples.


File Storage & Image Upload

Custom Upload (Your Server)

unlayer.registerCallback('image', (file, done) => {
  const data = new FormData();
  data.append('file', file.attachments[0]);

  fetch('/api/uploads', { method: 'POST', body: data })
    .then((r) => {
      if (!r.ok) throw new Error('Upload failed');
      return r.json();
    })
    .then((result) => done({ progress: 100, url: result.url }))
    .catch((err) => console.error('Upload error:', err));
});

Your backend should return:

{ "url": "https://your-cdn.com/images/uploaded-file.png" }

File Manager (Browse Uploaded Images)

Requires user.id in init — images are scoped per user:

unlayer.init({
  user: { id: 123 },                  // Required for file manager
  features: { userUploads: { enabled: true, search: true } },
});

unlayer.registerProvider('userUploads', (params, done) => {
  // params: { page, perPage, searchText }
  fetch(`/api/images?userId=123&page=${params.page}&perPage=${params.perPage}`)
    .then((r) => r.json())
    .then((data) => {
      done(
        data.items.map((img) => ({
          id: img.id,                  // Required
          location: img.url,           // Required — the image URL
          width: img.width,            // Optional but recommended
          height: img.height,          // Optional but recommended
          contentType: img.contentType, // Optional: 'image/png'
          source: 'user',              // Required: must be 'user'
        })),
        { hasMore: data.hasMore, page: params.page, total: data.total }
      );
    });
});

Your backend should return:

{
  "items": [
    { "id": "img_1", "url": "https://...", "width": 800, "height": 600, "contentType": "image/png" }
  ],
  "hasMore": true,
  "total": 42
}

See references/file-storage.md for upload progress with XHR, image deletion, and Amazon S3 setup.


Localization

unlayer.init({
  locale: 'es-ES',
  textDirection: 'rtl',        // 'ltr' | 'rtl' | null
  translations: {
    es: { Save: 'Guardar', Cancel: 'Cancelar' },
  },
});

Validation

// Global validator — runs on all content
unlayer.setValidator(async ({ html, design, defaultErrors }) => {
  return [...defaultErrors]; // Return modified error list
});

// Per-tool validator
unlayer.setToolValidator('text', async ({ html, defaultErrors }) => {
  return defaultErrors;
});

// Run audit on demand
unlayer.audit((result) => {
  // result: { status: 'FAIL' | 'PASS', errors: [{ id, icon, severity, title, description }] }
  if (result.status === 'FAIL') {
    console.log('Issues found:', result.errors);
  }
});

safeHtml (XSS Protection)

unlayer.init({
  safeHtml: true,   // Sanitize HTML via DOMPurify

  // Or with custom options:
  safeHtml: {
    domPurifyOptions: {
      FORCE_BODY: true,
    },
  },

  // WRONG: safeHTML (capital HTML) is DEPRECATED — use safeHtml
});

Common Mistakes

MistakeFix
safeHTML (uppercase)Use safeHtml (camelCase) — old casing deprecated
features.blocks = false hides tabIt disables blocks but NOT the tab — use tabs config
Deprecated colorPicker.presetsUse colorPicker.colors instead (string[] or ColorGroup[])
Missing user.id for file managerFile Manager requires user.id in init
Project Secret exposed in frontendNever put the secret in client code — generate HMAC server-side
Merge tag syntax mismatchMatch your template engine: {{var}} (Handlebars), ${var} (JS), {% %} (Jinja)

Troubleshooting

ProblemFix
Merge tags don't appearCheck setMergeTags() is called after editor:ready or passed in init()
HMAC signature rejectedEnsure user.id matches exactly what you signed, and secret is correct
File manager shows emptyCheck user.id is set, userUploads.enabled = true, provider returns correct format
Theme doesn't applyUse unlayer.setAppearance({ theme: 'modern_dark' }) or unlayer.setTheme('modern_dark') after init

Paid Features

FeatureHow to Enable
Custom CSS/JScustomCSS, customJS in init
Display conditionssetDisplayConditions()
Style guidesetStyleGuide()
Export Image/PDF/ZIPCloud API key required
AI featuresfeatures.ai
Collaborationfeatures.collaboration

Resources

Source

git clone https://github.com/unlayer/unlayer-skills/blob/main/unlayer-config/SKILL.mdView on GitHub

Overview

This skill configures Unlayer's editor via unlayer.init() options and runtime methods. It covers features, appearance, dynamic content, security, and file storage, aligning the editor with your workflow. You’ll wire in project keys, enable or disable features, customize fonts and tabs, and set up merge tags and validation.

How This Skill Works

It reads project credentials from the dashboard (Project ID, Project Secret for HMAC, Cloud API Key for exports) and applies settings via unlayer.init with features, appearance, fonts, tabs, and mergeTags. At runtime you can push changes with methods like unlayer.setAppearance, setTheme, and setMergeTags. This enables dynamic theming, content validation, and asset handling in your emails or pages.

When to Use It

  • When you need content validation and live previews during design
  • When branding requires a consistent theme, fonts, and tool layout
  • When you manage dynamic content with merge tags and nested groups
  • When securing assets, HMACs, and cloud image storage for exports
  • When localizing UI and fonts for multilingual or regional teams

Quick Start

  1. Step 1: Call unlayer.init with your chosen features, appearance, fonts, and tabs
  2. Step 2: Set up merge tags with unlayer.setMergeTags and test samples
  3. Step 3: Supply Project ID, Project Secret, and Cloud API Key from the Dashboard and test in staging

Best Practices

  • Map features to your workflow and disable unused flags to reduce complexity
  • Test merge tag syntax and nested groups with sample data before deployment
  • Secure project keys and API keys, rotating them per project
  • Preview emails or pages after enabling new themes or fonts
  • Document your init settings and maintain versioned configs

Example Use Cases

  • Enable modern_dark theme with right-hand tool panels and audit + undoRedo flags
  • Enable ai for draft generation and stockImages for campaigns
  • Add a custom font like Poppins with a live font URL
  • Configure merge tags with first_name, last_name and a nested company group
  • Turn on image uploads and file storage, with localization for a multilingual team

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers