npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pexelize/react-email-editor

v1.0.4

Published

React email editor component - drag-and-drop email builder for creating responsive email templates and newsletters. Build HTML emails visually with Pexelize.

Downloads

305

Readme

@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

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