Skip to main content
EmailEditor is the highest-level way to embed the React Email editor. It wraps Tiptap’s EditorProvider, mounts the default editor UI, and exposes a ref API for exporting HTML, plain text, and JSON. Use it when you want a batteries-included editor. If you need full control over the provider, overlays, or extension list, compose the editor from the lower-level APIs instead.
EmailEditor automatically imports @react-email/editor/themes/default.css, so the default bubble menus, slash command menu, and theme styles work without a separate CSS import.See styling for details on customizing the editor’s appearance.

Import

import { EmailEditor, type EmailEditorRef } from '@react-email/editor';

Signature

const EmailEditor: React.ForwardRefExoticComponent<
  EmailEditorProps & React.RefAttributes<EmailEditorRef>
>;

Default behavior

When rendered without a custom extensions prop, EmailEditor configures: If onUploadImage is provided, the image upload extension is appended automatically.

Props

content
Content
Initial editor content. Accepts TipTap JSON or an HTML string.
onUpdate
(ref: EmailEditorRef) => void
Called on every content update. The callback receives the same helper object exposed through the component ref.
onReady
(ref: EmailEditorRef) => void
Called once after the editor instance is mounted and ready.
theme
EditorThemeInput
default:"'basic'"
Built-in theme preset or custom theme object used by the default EmailTheming extension.
editable
boolean
default:"true"
Toggles read-only mode for the editor content.
placeholder
string
Overrides the default placeholder text. When omitted, paragraphs show Press '/' for commands.
bubbleMenu
{ hideWhenActiveNodes?: string[]; hideWhenActiveMarks?: string[] }
Configures when the default text bubble menu should stay hidden.
extensions
Extensions
Replaces the default extensions array. Use this when you want more control over the editor schema or plugin list.
onUploadImage
(file: File) => Promise<{ url: string }>
Enables image upload for paste, drop, and image insertion flows. Resolve with the final hosted image URL. See Image Upload for setup patterns and Image Upload API for the lower-level hook, commands, and types.
className
string
Applied to the underlying editor container element.
children
ReactNode
Additional UI rendered inside the internal EditorProvider. Children render as siblings of the editor content area, which makes layouts like split-pane editors and inspector sidebars work naturally.

bubbleMenu options

hideWhenActiveNodes
string[]
default:"['button']"
Prevents the default text bubble menu from appearing when one of these node types is active.
hideWhenActiveMarks
string[]
default:"['link']"
Prevents the default text bubble menu from appearing when one of these mark types is active.

Ref API

interface EmailEditorRef {
  getEmail(): Promise<{ html: string; text: string }>;
  getEmailHTML(): Promise<string>;
  getEmailText(): Promise<string>;
  getJSON(): JSONContent;
  editor: Editor | null;
}
getEmail
() => Promise<{ html: string; text: string }>
Serializes the current document to email-ready HTML and plain text in one call.
getEmailHTML
() => Promise<string>
Returns only the HTML export.
getEmailText
() => Promise<string>
Returns only the plain text export.
getJSON
() => JSONContent
Returns the current TipTap JSON document.
editor
Editor | null
The underlying TipTap editor instance. null until the editor is ready.
Before the editor is ready, the export methods resolve to empty output and getJSON() returns an empty document with type: 'doc' and an empty content array.

Example

import { EmailEditor, type EmailEditorRef } from '@react-email/editor';
import { useRef, useState } from 'react';

export function MyEditor() {
  const editorRef = useRef<EmailEditorRef>(null);
  const [html, setHtml] = useState('');

  const handleExport = async () => {
    if (!editorRef.current) return;
    setHtml(await editorRef.current.getEmailHTML());
  };

  return (
    <div>
      <EmailEditor
        ref={editorRef}
        content="<h1>Welcome</h1><p>Edit this email.</p>"
        theme="basic"
        onReady={(ref) => {
          console.log(ref.getJSON());
        }}
        onUpdate={(ref) => {
          console.log(ref.editor?.isEmpty);
        }}
      />

      <button onClick={handleExport}>Export HTML</button>

      {html && <textarea readOnly value={html} rows={12} />}
    </div>
  );
}

See also