@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
Maintainers
Keywords
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-editorEditor 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(); // PromiseExport
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); // voidUndo / Redo / Save
editor.undo(); // void
editor.redo(); // void
const canUndo = await editor.canUndo(); // Promise<boolean>
const canRedo = await editor.canRedo(); // Promise<boolean>
editor.save(); // voidPreview
editor.showPreview(device?); // void — 'desktop' | 'tablet' | 'mobile'
editor.hidePreview(); // voidCustom 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); // voidTabs & Branding
editor.updateTabs(tabs); // void
editor.setBrandingColors(config); // void
editor.registerColumns(cells); // voidDisplay Conditions
editor.setDisplayConditions(config); // voidAudit
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(); // voidEvents
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.
