eddyter
v1.4.19
Published
Plug and Play AI Rich Text Editor for any website, blog, CRM, ERP, or web app. Built on Lexical with AI writing assistance, dark mode, and real-time collaboration.
Downloads
1,916
Readme
Eddyter
Plug and Play AI Rich Text Editor for any website, blog, CRM, ERP, or web app — built on Lexical with dark mode support and API key authentication.

Resources
- Docs — Full API reference and integration guides
- What is Eddyter? Why Developers Are Switching to This AI Editor (2026) — YouTube
- Integrate Eddyter in 30 Minutes Using AI Tools Cursor, Claude, Lovable — YouTube
Installation
npm install eddyter
# or
yarn add eddyter
# or
pnpm add eddyterCompatibility
| Requirement | Version | |-------------|---------| | React | 18.2+ or 19.x | | React DOM | 18.2+ or 19.x | | Node.js | 16+ |
Quick Start
1. Import styles
import 'eddyter/style.css';Important: The stylesheet is required for tables, toolbars, and all editor components to render correctly.
2. Get your API key
- Create an account at eddyter.com
- Navigate to License Keys in your dashboard
- Copy your API key
3. Add the editor
import React from 'react';
import {
ConfigurableEditorWithAuth,
EditorProvider,
defaultEditorConfig
} from 'eddyter';
import 'eddyter/style.css';
function App() {
const apiKey = process.env.NEXT_PUBLIC_EDITOR_API_KEY!;
const currentUser = {
id: 'user-123',
name: 'John Doe',
email: '[email protected]',
avatar: 'https://example.com/avatar.jpg' // optional
};
return (
<EditorProvider
defaultFontFamilies={defaultEditorConfig.defaultFontFamilies}
currentUser={currentUser}
>
<ConfigurableEditorWithAuth
apiKey={apiKey}
onChange={(html) => console.log('Content:', html)}
initialContent="<p>Start writing...</p>"
mentionUserList={['Alice', 'Bob', 'Charlie']}
onAuthSuccess={() => console.log('Editor ready!')}
onAuthError={(error) => console.error('Auth failed:', error)}
/>
</EditorProvider>
);
}Features
Text & Formatting
- Bold, italic, underline, strikethrough, subscript, superscript
- Text color and background highlight with color picker
- 20+ font families with adjustable font sizes
- Text alignment (left, center, right, justify)
- Line height and letter spacing controls
Lists & Structure
- Bullet lists, numbered lists (decimal, alpha, roman)
- Interactive checklists with strikethrough
- Headings (H1-H6), blockquotes
- Horizontal rules
Tables
- Insert/delete rows and columns, merge cells
- Drag-to-resize columns and rows
- Header row styling
- Row striping with custom colors
- Right-click context menu for table actions
Media
- Image upload with drag-drop and 8-point resize handles
- Video embed with drag-drop and paste support
- File attachments (downloadable files)
- Link insertion with floating editor
- Automatic link preview on hover
- Rich embeds for external content (YouTube, etc.)
AI Features (Premium)
- AI Chat assistant for content help
- Smart autocomplete (AI-powered text suggestions)
- Real-time grammar check and corrections
- Text enhancement (improve, shorten, expand)
- Tone adjustment (formal, casual, professional)
- AI image generation from text prompts
Advanced
- Slash commands (
/for quick formatting) - @Mentions with customizable user list
- Inline comments with bubble UI and sidebar
- Note panels (info, warning, error, success)
- Code blocks with syntax highlighting
- Interactive charts
- Digital signature capture
- Voice input / transcription
- Export to PDF
- HTML view toggle
- Drag-and-drop block reordering
- Markdown shortcuts
Dark Mode
The editor automatically detects your app's theme:
- Checks for
darkclass on<html>or<body> - Falls back to
prefers-color-scheme: darksystem preference - Or set explicitly via the
darkModeprop onEditorProvider
Preview Mode
Display saved editor content in read-only mode with interactive features:
<ConfigurableEditorWithAuth
apiKey={apiKey}
mode="preview"
initialContent={savedHtml}
onPreviewClick={() => setMode('edit')}
containerClassName="my-preview-styles"
/>API Reference
<EditorProvider>
Provides authentication and configuration context. Must wrap the editor component.
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| children | ReactNode | Yes | Editor component to render |
| defaultFontFamilies | string[] | No | Font family names for the font selector |
| currentUser | CurrentUser | No | Current user for comments feature |
| enableLinkPreview | boolean | No | Enable link preview on hover (default: true) |
| apiKey | string | No | API key for link preview in read-only mode |
CurrentUser Type
interface CurrentUser {
id: string;
name: string;
email: string;
avatar?: string;
}<ConfigurableEditorWithAuth>
The main editor component with authentication.
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| apiKey | string | Yes | Your API key for authentication |
| initialContent | string | No | Initial HTML content |
| onChange | (html: string) => void | No | Content change callback |
| defaultFontFamilies | string[] | No | Font names for the font selector |
| mentionUserList | string[] | No | Usernames for @mention feature |
| onAuthSuccess | () => void | No | Called when authentication succeeds |
| onAuthError | (error: string) => void | No | Called when authentication fails |
| customVerifyKey | (key: string) => Promise<ApiResponse> | No | Custom key verification function |
| mode | "edit" \| "preview" | No | Editor mode (default: "edit") |
| containerClassName | string | No | CSS class for the outermost editor container |
| contentClassName | string | No | CSS class for the specific editor/preview content area |
| editor | { maxHeight?: React.CSSProperties["maxHeight"] } | No | Editor container options (maxHeight) |
| onPreviewClick | () => void | No | Click handler for preview mode |
| enableReactNativeBridge | boolean | No | Enable React Native WebView bridge |
| onEditorReady | () => void | No | Called when editor is fully loaded |
| onFocus | () => void | No | Called on editor focus |
| onBlur | () => void | No | Called on editor blur |
| onHeightChange | (height: number) => void | No | Called when editor height changes |
| toolbar | { mode?: "sticky" \| "static"; offset?: number; zIndex?: number } | No | Toolbar behavior config (default: { mode: "sticky", offset: 20, zIndex: 1000 }) |
Toolbar Configuration
Use the toolbar prop to control sticky/static toolbar behavior:
<ConfigurableEditorWithAuth
apiKey="your-api-key"
toolbar={{ mode: "sticky", offset: 64, zIndex: 1200 }}
/>Modes:
mode: "sticky"-> toolbar detaches/sticks while scrolling and appliesoffset+zIndexmode: "static"-> toolbar stays attached and ignoresoffset+zIndexeven if provided
Defaults:
const defaultToolbar = {
mode: "sticky",
offset: 20,
zIndex: 1000
};In static mode, if you want only the editor content area to scroll, pass a maxHeight using editor:
<ConfigurableEditorWithAuth
apiKey="your-api-key"
toolbar={{ mode: "static" }}
editor={{ maxHeight: 600 }}
/>If maxHeight is not provided, the full page/container scrolls normally with the toolbar.
Examples
Basic Editor
import { ConfigurableEditorWithAuth, EditorProvider } from 'eddyter';
import 'eddyter/style.css';
export default function BasicEditor() {
return (
<EditorProvider>
<ConfigurableEditorWithAuth
apiKey="your-api-key"
onAuthSuccess={() => console.log('Ready!')}
/>
</EditorProvider>
);
}Editor with State Management
import { useState } from 'react';
import { ConfigurableEditorWithAuth, EditorProvider } from 'eddyter';
import 'eddyter/style.css';
export default function EditorWithState() {
const [content, setContent] = useState('<p>Start writing...</p>');
const handleSave = async () => {
await fetch('/api/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content })
});
};
return (
<div>
<EditorProvider>
<ConfigurableEditorWithAuth
apiKey="your-api-key"
initialContent={content}
onChange={setContent}
/>
</EditorProvider>
<button onClick={handleSave}>Save</button>
</div>
);
}Editor with Comments & Mentions
import { ConfigurableEditorWithAuth, EditorProvider } from 'eddyter';
import 'eddyter/style.css';
export default function EditorWithComments({ user }) {
const currentUser = {
id: user.id,
name: user.name,
email: user.email,
avatar: user.avatarUrl
};
return (
<EditorProvider currentUser={currentUser}>
<ConfigurableEditorWithAuth
apiKey="your-api-key"
mentionUserList={['Alice', 'Bob', 'Charlie']}
/>
</EditorProvider>
);
}Custom API Key Verification
import { ConfigurableEditorWithAuth, EditorProvider } from 'eddyter';
import 'eddyter/style.css';
export default function EditorWithCustomAuth() {
const customVerifyKey = async (apiKey: string) => {
try {
const response = await fetch('/api/verify-key', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ apiKey })
});
const data = await response.json();
return { success: data.valid, message: data.message || 'Verified' };
} catch {
return { success: false, message: 'Verification failed' };
}
};
return (
<EditorProvider>
<ConfigurableEditorWithAuth
apiKey="your-api-key"
customVerifyKey={customVerifyKey}
/>
</EditorProvider>
);
}Link Preview
The editor includes automatic link preview on hover.
- Inside the editor: Works automatically after authentication
- Read-only content: Pass
apiKeytoEditorProvider - Disable: Set
enableLinkPreview={false}onEditorProvider
// Read-only content with link preview
<EditorProvider apiKey="your-api-key">
<div dangerouslySetInnerHTML={{ __html: savedHtml }} />
</EditorProvider>
// Disable link preview
<EditorProvider enableLinkPreview={false}>
{/* content */}
</EditorProvider>React Native Integration
Use Eddyter in React Native via WebView by loading a deployed version of the editor.
npm install react-native-webviewimport React, { useRef, useState, useCallback } from 'react';
import { View, ActivityIndicator, KeyboardAvoidingView, Platform } from 'react-native';
import { WebView, WebViewMessageEvent } from 'react-native-webview';
interface RichTextEditorProps {
editorBaseUrl: string;
apiKey: string;
initialContent?: string;
theme?: 'light' | 'dark';
style?: object;
onChange?: (content: string) => void;
onReady?: () => void;
onAuthSuccess?: () => void;
onAuthError?: (error: string) => void;
}
export const RichTextEditor: React.FC<RichTextEditorProps> = ({
editorBaseUrl,
apiKey,
initialContent,
theme = 'light',
style,
onChange,
onReady,
onAuthSuccess,
onAuthError,
}) => {
const webViewRef = useRef<WebView>(null);
const [isLoading, setIsLoading] = useState(true);
const buildEditorUrl = () => {
const baseUrl = editorBaseUrl.replace(/\/$/, '');
const params = new URLSearchParams();
if (apiKey) params.append('apiKey', apiKey);
if (theme) params.append('theme', theme);
return `${baseUrl}?${params.toString()}`;
};
const handleMessage = useCallback((event: WebViewMessageEvent) => {
try {
const message = JSON.parse(event.nativeEvent.data);
switch (message.type) {
case 'EDITOR_READY':
setIsLoading(false);
onReady?.();
if (initialContent && webViewRef.current) {
webViewRef.current.postMessage(
JSON.stringify({ type: 'SET_CONTENT', payload: { content: initialContent } })
);
}
break;
case 'CONTENT_CHANGE':
onChange?.(message.payload?.content || '');
break;
case 'AUTH_SUCCESS':
onAuthSuccess?.();
break;
case 'AUTH_ERROR':
onAuthError?.(message.payload?.error);
break;
}
} catch (e) {
console.warn('[RichTextEditor] Failed to parse message:', e);
}
}, [onChange, onReady, onAuthSuccess, onAuthError, initialContent]);
return (
<View style={[{ flex: 1 }, style]}>
<WebView
ref={webViewRef}
source={{ uri: buildEditorUrl() }}
style={{ flex: 1 }}
onMessage={handleMessage}
javaScriptEnabled={true}
domStorageEnabled={true}
keyboardDisplayRequiresUserAction={false}
/>
{isLoading && (
<View style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, justifyContent: 'center', alignItems: 'center' }}>
<ActivityIndicator size="large" />
</View>
)}
</View>
);
};Message Protocol
| Message Type | Direction | Description |
|---|---|---|
| EDITOR_READY | Editor → RN | Editor has finished loading |
| CONTENT_CHANGE | Editor → RN | Content was modified ({ content: string }) |
| AUTH_SUCCESS | Editor → RN | Authentication succeeded |
| AUTH_ERROR | Editor → RN | Authentication failed ({ error: string }) |
| SET_CONTENT | RN → Editor | Set editor content ({ content: string }) |
Exports
// Components
import {
ConfigurableEditorWithAuth, // Main editor with auth
ConfigurableEditor, // Editor without auth wrapper
EditorProvider, // Context provider
LinkPreviewHover, // Standalone link preview component
} from 'eddyter';
// Hooks & utilities
import {
useEditor, // Access editor context
useHtmlView, // Access HTML view state
verifyApiKey, // Verify API key programmatically
useReactNativeBridge, // React Native bridge hook
isReactNativeWebView, // Check if running in RN WebView
} from 'eddyter';
// Config
import { defaultEditorConfig } from 'eddyter';
// Types
import type {
CurrentUser,
EditorConfigTypes,
LinkPreviewHoverProps,
ReactNativeBridgeConfig,
ReactNativeMessage,
ReactNativeMessageType,
} from 'eddyter';License
Eddyter is proprietary software.
- Free for evaluation and non-commercial use
- Commercial use requires a paid license
- SaaS, redistribution, and competing products are prohibited without permission
For commercial licensing, visit eddyter.com
