Skip to content

Pexelize/pexelize-react-email-editor

Repository files navigation

Pexelize Email Editor - React Email Template Builder

npm version license

@pexelize/react-email-editor

React component for building email templates with drag-and-drop. Embed a full-featured email editor into your React app — create responsive HTML emails, newsletters, transactional email templates, and email marketing campaigns visually without writing code.

Pexelize is a modern email builder and email template editor that lets your users design professional emails with a visual drag-and-drop interface.

Website | Documentation | Dashboard

Pexelize React Email Editor - Drag and Drop Email Template Builder

Features

  • Drag-and-drop email template builder with 20+ content blocks
  • Responsive HTML email output compatible with all major email clients
  • Newsletter editor with merge tags, dynamic content, and display conditions
  • Visual email designer — no HTML/CSS knowledge required for end users
  • Export to HTML, JSON, image, PDF, or ZIP
  • Built-in image editor, AI content generation, and collaboration tools
  • Full TypeScript support
  • Lightweight React wrapper — just a single component or hook

Installation

# npm
npm install @pexelize/react-email-editor

# yarn
yarn add @pexelize/react-email-editor

# pnpm
pnpm add @pexelize/react-email-editor

Editor Key

An editorKey is required to use the editor. You can get one by creating a project on the Pexelize Developer Dashboard.

Quick Start

import { useRef } from "react";
import { PexelizeEditor, PexelizeEditorRef } from "@pexelize/react-email-editor";

function EmailBuilder() {
  const editorRef = useRef<PexelizeEditorRef>(null);

  const handleExport = async () => {
    const editor = editorRef.current?.editor;
    if (!editor) return;

    const html = await editor.exportHtml();
    console.log("HTML:", html);
  };

  return (
    <div style={{ height: "100vh" }}>
      <button onClick={handleExport}>Export HTML</button>
      <PexelizeEditor
        ref={editorRef}
        editorKey="your-editor-key"
        editorMode="email"
        minHeight="600px"
        onReady={(editor) => console.log("Editor ready!")}
        onChange={(data) => console.log("Design changed:", data.type)}
        onError={(error) => console.error("Editor error:", error)}
      />
    </div>
  );
}

Complete Example

import { useRef, useState, useCallback } from "react";
import {
  PexelizeEditor,
  PexelizeEditorRef,
  DesignJson,
} from "@pexelize/react-email-editor";

function AdvancedEmailBuilder() {
  const editorRef = useRef<PexelizeEditorRef>(null);
  const [isDirty, setIsDirty] = useState(false);

  const handleReady = useCallback((editor) => {
    // Set merge tags (must pass a MergeTagsConfig object)
    editor.setMergeTags({
      customMergeTags: [
        { name: "First Name", value: "{{first_name}}" },
        { name: "Last Name", value: "{{last_name}}" },
        { name: "Company", value: "{{company}}" },
      ],
      excludeDefaults: false,
      sort: true,
    });

    // Set custom fonts
    editor.setFonts({
      showDefaultFonts: true,
      customFonts: [{ label: "Brand Font", value: "BrandFont, sans-serif" }],
    });

    // Load saved design if available
    const savedDesign = localStorage.getItem("email-design");
    if (savedDesign) {
      editor.loadDesign(JSON.parse(savedDesign));
    }
  }, []);

  const handleChange = useCallback(
    (data: { design: DesignJson; type: string }) => {
      setIsDirty(true);
      localStorage.setItem("email-design", JSON.stringify(data.design));
    },
    [],
  );

  const handleExportHtml = async () => {
    const editor = editorRef.current?.editor;
    if (!editor) return;

    const html = await editor.exportHtml();
    const blob = new Blob([html], { type: "text/html" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = "email.html";
    a.click();
  };

  const handleExportImage = async () => {
    const editor = editorRef.current?.editor;
    if (!editor) return;

    const data = await editor.exportImage();
    window.open(data.url, "_blank");
  };

  return (
    <div style={{ height: "100vh", display: "flex", flexDirection: "column" }}>
      <div
        style={{
          padding: 12,
          borderBottom: "1px solid #ddd",
          display: "flex",
          gap: 8,
        }}
      >
        <button onClick={() => editorRef.current?.editor?.undo()}>Undo</button>
        <button onClick={() => editorRef.current?.editor?.redo()}>Redo</button>
        <button
          onClick={() => editorRef.current?.editor?.showPreview("desktop")}
        >
          Preview
        </button>
        <button onClick={handleExportHtml}>Export HTML</button>
        <button onClick={handleExportImage}>Export Image</button>
        {isDirty && <span style={{ color: "orange" }}>Unsaved changes</span>}
      </div>

      <PexelizeEditor
        ref={editorRef}
        editorKey="your-editor-key"
        editorMode="email"
        height="100%"
        designMode="live"
        options={{
          appearance: { theme: "light" },
          features: {
            preview: true,
            undoRedo: true,
            imageEditor: true,
          },
        }}
        onReady={handleReady}
        onChange={handleChange}
        onError={(error) => console.error(error.message)}
      />
    </div>
  );
}

export default AdvancedEmailBuilder;

Props

Prop Type Required Default Description
editorKey string Yes Editor key for authentication
design DesignJson | ModuleData | null No undefined Initial design to load
editorMode EditorMode No "email" | "web" | "popup"
contentType "module" No Single-row module editor mode
options EditorOptions No {} All editor configuration
popup PopupConfig No Popup config (only when editorMode is "popup")
collaboration boolean | CollaborationFeaturesConfig No Collaboration features
user UserInfo No User info for session/collaboration
designMode "edit" | "live" No "live" Template permissions mode
height string | number No Editor height
minHeight string | number No "600px" Minimum editor height
callbacks Omit<PexelizeCallbacks, "onReady" | "onLoad" | "onChange" | "onError"> No SDK callbacks (excluding those handled by dedicated props)
className string No CSS class for the outer container
style React.CSSProperties No Inline styles for the outer container

| onReady | (editor: PexelizeSDK) => void | No | — | Called when the editor is ready | | onLoad | () => void | No | — | Called when a design is loaded | | onChange | (data: { design: DesignJson; type: string }) => void | No | — | Called when the design changes | | onError | (error: Error) => void | No | — | Called on error | | onComment | (action: CommentAction) => void | No | — | Called on comment events |

Ref

Use a ref to access the SDK instance:

const editorRef = useRef<PexelizeEditorRef>(null);

// PexelizeEditorRef shape:
// {
//   editor: PexelizeSDK | null;
//   isReady: () => boolean;
// }

Hook API

The usePexelizeEditor hook provides a convenient way to access the editor:

import { PexelizeEditor, usePexelizeEditor } from "@pexelize/react-email-editor";

function MyEditor() {
  const { ref, editor, isReady } = usePexelizeEditor();

  return (
    <div>
      <button
        onClick={async () => {
          const html = await editor?.exportHtml();
          console.log(html);
        }}
        disabled={!isReady}
      >
        Export
      </button>
      <PexelizeEditor ref={ref} editorKey="your-editor-key" />
    </div>
  );
}

Returns: { ref, editor, isReady }ref is passed to the component, editor is the SDK instance (or null), and isReady is a boolean.

SDK Methods Reference

Access the SDK via editorRef.current?.editor or the editor value from usePexelizeEditor(). All export and getter methods return Promises.

Design

editor.loadDesign(design, options?);                   // void
const result = await editor.loadDesignAsync(design, options?);
// => { success, validRowsCount, invalidRowsCount, errors? }
editor.loadBlank(options?);                            // void
const { html, json } = await editor.getDesign();       // Promise

Export

All export methods are Promise-based. There are no callback overloads.

const html = await editor.exportHtml(options?);        // Promise<string>
const json = await editor.exportJson();                // Promise<DesignJson>
const text = await editor.exportPlainText();           // Promise<string>
const imageData = await editor.exportImage(options?);  // Promise<ExportImageData>
const pdfData = await editor.exportPdf(options?);      // Promise<ExportPdfData>
const zipData = await editor.exportZip(options?);      // Promise<ExportZipData>
const values = await editor.getPopupValues();          // Promise<PopupValues | null>

Merge Tags

setMergeTags accepts a MergeTagsConfig object, not a plain array.

editor.setMergeTags({
  customMergeTags: [
    { name: "First Name", value: "{{first_name}}" },
    { name: "Company", value: "{{company}}" },
  ],
  excludeDefaults: false,
  sort: true,
});
const tags = await editor.getMergeTags(); // Promise<(MergeTag | MergeTagGroup)[]>

Special Links

setSpecialLinks accepts a SpecialLinksConfig object.

editor.setSpecialLinks({
  customSpecialLinks: [{ name: "Unsubscribe", href: "{{unsubscribe_url}}" }],
  excludeDefaults: false,
});
const links = await editor.getSpecialLinks(); // Promise<(SpecialLink | SpecialLinkGroup)[]>

Modules

editor.setModules(modules); // void
editor.setModulesLoading(loading); // void
const modules = await editor.getModules(); // Promise<Module[]>

Fonts

editor.setFonts(config); // void
const fonts = await editor.getFonts(); // Promise<FontsConfig>

Body Values

editor.setBodyValues({
  backgroundColor: "#f5f5f5",
  contentWidth: "600px",
});
const values = await editor.getBodyValues(); // Promise<SetBodyValuesOptions>

Editor Configuration

editor.setOptions(options); // void — Partial<EditorOptions>
editor.setToolsConfig(toolsConfig); // void
editor.setEditorMode(mode); // void
editor.setEditorConfig(config); // void
const config = await editor.getEditorConfig(); // Promise<EditorBehaviorConfig>

Locale, Language & Text Direction

editor.setLocale(locale, translations?);            // void
editor.setLanguage(language);                       // void
const lang = await editor.getLanguage();            // Promise<Language | null>
editor.setTextDirection(direction);                 // void — 'ltr' | 'rtl'
const dir = await editor.getTextDirection();        // Promise<TextDirection>

Appearance

editor.setAppearance(appearance); // void

Undo / Redo / Save

editor.undo(); // void
editor.redo(); // void
const canUndo = await editor.canUndo(); // Promise<boolean>
const canRedo = await editor.canRedo(); // Promise<boolean>
editor.save(); // void

Preview

editor.showPreview(device?);  // void — 'desktop' | 'tablet' | 'mobile'
editor.hidePreview();         // void

Custom Tools

await editor.registerTool(config); // Promise<void>
await editor.unregisterTool(toolId); // Promise<void>
const tools = await editor.getTools(); // Promise<Array<{ id, label, baseToolType }>>

Custom Widgets

await editor.createWidget(config); // Promise<void>
await editor.removeWidget(widgetName); // Promise<void>

Collaboration & Comments

editor.showComment(commentId); // void
editor.openCommentPanel(rowId); // void

Tabs & Branding

editor.updateTabs(tabs); // void
editor.setBrandingColors(config); // void
editor.registerColumns(cells); // void

Display Conditions

editor.setDisplayConditions(config); // void

Audit

const result = await editor.audit(options?);  // Promise<AuditResult>

Asset Management

const { success, url, error } = await editor.uploadImage(file, options?);
const { assets, total } = await editor.listAssets(options?);
const { success, error } = await editor.deleteAsset(assetId);
const folders = await editor.listAssetFolders(parentId?);
const folder = await editor.createAssetFolder(name, parentId?);
const info = await editor.getStorageInfo();

Status & Lifecycle

editor.isReady(); // boolean
editor.destroy(); // void

Events

Subscribe to editor events using addEventListener:

const unsubscribe = editor.addEventListener("design:updated", (data) => {
  console.log("Design changed:", data);
});

// Or remove manually
editor.removeEventListener("design:updated", callback);

Available Events

Event Description
editor:ready Editor initialized
design:loaded Design loaded
design:updated Design changed
design:saved Design saved
row:selected Row selected
row:unselected Row unselected
column:selected Column selected
column:unselected Column unselected
content:selected Content block selected
content:unselected Content block unselected
content:modified Content block modified
content:added Content block added
content:deleted Content block deleted
preview:shown Preview opened
preview:hidden Preview closed
image:uploaded Image uploaded successfully
image:error Image upload error
export:html HTML exported
export:plainText Plain text exported
export:image Image exported
save Save triggered
save:success Save succeeded
save:error Save failed
template:requested Template requested
element:selected Element selected
element:deselected Element deselected
export Export triggered
displayCondition:applied Display condition applied
displayCondition:removed Display condition removed
displayCondition:updated Display condition updated

TypeScript

All SDK types are re-exported from the package:

import type {
  PexelizeEditorRef,
  PexelizeEditorProps,
  DesignJson,
  EditorOptions,
  MergeTag,
  MergeTagGroup,
  MergeTagsConfig,
  SpecialLink,
  SpecialLinkGroup,
  SpecialLinksConfig,
  FontsConfig,
  EditorMode,
  PopupConfig,
  UserInfo,
  CollaborationFeaturesConfig,
  CommentAction,
} from "@pexelize/react-email-editor";

Contributing

See CONTRIBUTING.md for guidelines on how to contribute to this project.

License

MIT

About

Embeddable email editor for React — build responsive HTML email templates, newsletters, and marketing campaigns with drag-and-drop

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors