Skip to content

Configuration

Pass an EditorConfig object to the config prop to toggle features, rename actions, set the default content width, and provide starter templates.

ts
import type { EditorConfig } from 'vue-mail-editor'

const config: EditorConfig = {
  contentWidth: 640,
  devices: ['desktop', 'mobile'],
  deviceWidths: { mobile: 320, tablet: 768 },
  actions: { import: false, saveTemplate: true },
  labels: { save: 'Publish', export: 'Get HTML' },
  templates: [ /* your starter templates */ ],
  autosaveMs: 1000, // localStorage debounce only; ignored when storage="none"
}
js
const config = {
  contentWidth: 640,
  devices: ['desktop', 'mobile'],
  deviceWidths: { mobile: 320, tablet: 768 },
  actions: { import: false, saveTemplate: true },
  labels: { save: 'Publish', export: 'Get HTML' },
  templates: [ /* your starter templates */ ],
  autosaveMs: 1000, // localStorage debounce only; ignored when storage="none"
}
vue
<EmailEditor :config="config" />

Fields

FieldTypeDefaultDescription
contentWidthnumbertemplate defaultEmail content width (px) for new designs.
devices('desktop' | 'tablet' | 'mobile')[]all threeWhich device-preview toggles to show.
deviceWidthsDeviceWidthsmobile 375, tablet 600, desktop = content widthOverride the canvas/preview width (px) per device.
actionsEditorActionsall on (saveTemplate off)Show/hide built-in top-bar actions.
labelsEditorLabelsEnglish defaultsRename built-in action labels & tooltips.
labeledActionsbooleanfalseShow text labels on the Save/Export buttons (icon-only otherwise).
variableSyntax'double' | 'triple''triple'Merge-token delimiter: {{name}} vs {{{name}}}.
variablesDesignVariable[][]Predefined template variables seeded into new designs.
lockVariablesbooleanfalseLock the registry to the variables you provide — users can insert them but not create or delete any.
templatesTemplateDef[]built-insReplace the starter-template gallery.
autosaveMsnumber800localStorage autosave debounce (ms).
metaboolean | MetaFieldsoffEmail metadata header (subject/from/reply-to/preview). true shows all fields.

deviceWidths — per-device preview size

The device toggles in the top bar (desktop / tablet / mobile) render the canvas and the real-preview at a fixed width. Override any of them:

ts
const config: EditorConfig = {
  deviceWidths: {
    mobile: 320,   // default 375
    tablet: 768,   // default 600
    desktop: 700,  // default: the design's own content width
  },
}
  • Omit a device to keep its default (mobile 375, tablet 600, desktop = the design's content width).
  • The width drives both the editable canvas and the preview iframe.
  • The desktop preview always renders above the 600 px responsive breakpoint, so multi-column rows stay side-by-side there even if you set a small desktop width — that value still caps the email content and centers it.
  • Pair with devices to control which toggles appear.

The "Choose a template" picker's cards. Passing templates replaces the built-in gallery. Each entry is a TemplateDef:

ts
interface TemplateDef {
  id: string
  name: string
  description: string
  accent: string        // Tailwind gradient classes for the thumbnail card
  build: () => Design   // returns the design loaded when the card is picked
}
ts
const config: EditorConfig = {
  templates: [
    {
      id: 'welcome',
      name: 'Welcome',
      description: 'Onboarding email with logo, intro and CTA.',
      accent: 'from-indigo-400 to-violet-500',
      build: () => myWelcomeDesign, // any Design object
    },
  ],
}

These are static starter designs. To surface designs your users saved (via onSaveTemplate) in this same gallery, map each stored design into a TemplateDef whose build() returns it — see Reuse saved templates in the gallery.

meta — email header fields

A header card above the email with From, Reply-To, Subject, and Preview text (Reply-To and Preview collapse to chips until used; Subject and Preview support {{ variables). It's off by default — opt in:

ts
const config: EditorConfig = {
  meta: true,                                 // show all four fields
  // meta: { from: true, subject: true },     // or pick fields
}

All four fields are stored on design.meta (subject, from, replyTo, preview) — read them from the saved design when you send. Subject/from/reply-to aren't in the exported HTML; the preview (design.meta.preview) is also rendered as the email's hidden preheader.

Custom header — the #meta slot

The built-in fields are plain inputs and can't verify a sender domain (that's a server-side concern). For a validated From — or any custom layout — replace the whole header with the #meta scoped slot.

You own the styling

The slot content is fully custom — the editor doesn't style it, and its CSS baseline strips the default border/padding from <input> / <select>. Style your fields (like the <style> below) or they'll render bare.

vue
<script setup>
import { ref, onMounted } from 'vue'
import { EmailEditor } from 'vue-mail-editor'

const design = ref()
// Your verified senders — loaded from your backend (dynamic data).
const verifiedSenders = ref([])
onMounted(async () => {
  verifiedSenders.value = await fetch('/api/verified-senders').then((r) => r.json())
  // e.g. ['hello@acme.com', 'news@acme.com']
})
</script>

<template>
  <EmailEditor v-model="design">
    <template #meta="{ meta, setMeta }">
      <div class="meta-card">
        <label class="meta-row">
          <span>From</span>
          <select :value="meta.from" @change="setMeta({ from: $event.target.value })">
            <option value="">Select a verified sender…</option>
            <option v-for="d in verifiedSenders" :key="d" :value="d" v-text="d" />
          </select>
        </label>
        <label class="meta-row">
          <span>Subject</span>
          <input :value="meta.subject" @input="setMeta({ subject: $event.target.value })" />
        </label>
        <label class="meta-row">
          <span>Preview</span>
          <input :value="meta.preview" @input="setMeta({ preview: $event.target.value })" />
        </label>
      </div>
    </template>
  </EmailEditor>
</template>

<style scoped>
.meta-card {
  border: 1px solid #e2e8f0; border-radius: 12px; background: #fff; padding: 2px 16px;
}
.meta-row {
  display: flex; align-items: center; gap: 12px;
  min-height: 44px; border-bottom: 1px solid #f1f5f9;
}
.meta-row:last-child { border-bottom: 0; }
.meta-row span { width: 72px; font-size: 13px; font-weight: 500; color: #64748b; }
.meta-row input,
.meta-row select {
  flex: 1; border: 1px solid #e2e8f0; border-radius: 8px; padding: 6px 10px; font-size: 14px;
}
</style>

Slot props:

  • meta — read the current subject, from, replyTo and preview.
  • setMeta(patch) — write any of them; a preview key routes to the preheader, and any custom key (e.g. campaignId) is stored on design.meta and round-trips with the design.

verifiedSenders is your own data — fetch it from your backend into a ref and v-for the <option>s. The editor isn't involved; when the user picks one, setMeta({ from }) writes it to design.meta.from, which you read back on send.

Alternatively, keep the built-in header and validate design.meta.from server-side on save — reject it if it isn't a verified domain.

actions — show / hide built-ins

Every action defaults to true except saveTemplate (defaults false). Set a flag to false to hide that control.

ts
const config: EditorConfig = {
  actions: {
    undo: true, preview: true, theme: true, fullscreen: true,
    templates: false, new: false, import: false,
    save: true, saveTemplate: true, export: true,
  },
}
js
const config = {
  actions: {
    undo: true, preview: true, theme: true, fullscreen: true,
    templates: false, new: false, import: false,
    save: true, saveTemplate: true, export: true,
  },
}

The fullscreen action expands the editor to fill the whole window (a second click or Esc exits).

labels — rename built-ins

Relabel button text and tooltips without replacing the top bar. Every key is optional; unset keys keep their defaults.

ts
const config: EditorConfig = {
  labels: {
    brand: 'Acme Mailer',
    save: 'Publish',
    export: 'Get HTML',
    saveTemplate: 'Save template',
    undo: 'Undo', redo: 'Redo', preview: 'Preview', fullscreen: 'Fullscreen',
    templates: 'Templates', new: 'New design', import: 'Import JSON',
  },
}
js
const config = {
  labels: {
    brand: 'Acme Mailer',
    save: 'Publish',
    export: 'Get HTML',
    saveTemplate: 'Save template',
    undo: 'Undo', redo: 'Redo', preview: 'Preview', fullscreen: 'Fullscreen',
    templates: 'Templates', new: 'New design', import: 'Import JSON',
  },
}

brand only applies when you have not overridden the #header-brand slot.

labeledActions — Save / Export button style

The primary Save and Export buttons render icon-only by default, with their label shown as a tooltip. Set labeledActions: true to display the text label next to the icon.

ts
const config: EditorConfig = { labeledActions: true }

variableSyntax — merge-token delimiter

Template variables export as triple curly braces by default — {{{name}}} (Mustache/Handlebars raw). If your templating engine / ESP uses double braces, set variableSyntax: 'double' to emit {{name}} instead:

ts
const config: EditorConfig = { variableSyntax: 'double' } // {{name}} tokens

This affects both the chips shown in the editor and the exported HTML tokens. Keep it consistent with what your backend merges at send time.

Don't change it on existing content

Designs are stored independently of the delimiter (chips carry the variable name, not the braces), so switching is safe for stored designs. But any already-exported HTML you saved elsewhere used the old delimiter — re-export after changing this.

lockVariables — host-managed registry

By default users can create and delete template variables from the editor. If the merge tags come from your system (a CRM contact schema, an ESP's merge fields), a user-invented variable would export a token your backend can't resolve. Set lockVariables: true to make the registry read-only:

ts
const config: EditorConfig = {
  variables: [
    { name: 'first_name', type: 'string', fallback: 'there' },
    { name: 'order_total', type: 'number', fallback: '0.00' },
  ],
  lockVariables: true,
}

Users can still insert those variables and edit each one's fallback value (so previews and fallback exports look right). The Create Variable row, the Delete button and the type selector are hidden, and create() / remove() no-op — so the set of tokens that can reach your backend is exactly the set you passed in. See Template variables.

autosaveMs — important caveat

autosaveMs only debounces the built-in localStorage autosave used when storage="local" (the default). With storage="none" it does nothing — you own the save cadence. See Server-side autosave.

Released under the MIT License.