# Eddyter — API Reference & Integration Guide > Eddyter is a plug-and-play, AI-native rich text editor built on Meta's Lexical framework. Drop it into React, vanilla JavaScript, Angular, Svelte, Vue, or Laravel in about 10 minutes — without managing infrastructure, storage, or AI. **Compatibility:** React 18.2+ / 19.x (Next.js App & Pages Router, Vite, CRA, any Webpack-based React framework). Also available as a framework-agnostic SDK (`@eddyter/core`, ES modules or ` ``` Pin a version in production: `https://unpkg.com/@eddyter/core@1.0.1/dist/editor.iife.js` (or jsDelivr / self-hosted `editor.iife.js`). ### Angular — `@eddyter/angular` (Angular 17–21) Standalone `` component with `@angular/forms` `ControlValueAccessor` support (`[(ngModel)]` or `[(value)]`). ```bash npm install @eddyter/angular ``` ```ts import { Component } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { Eddyter } from '@eddyter/angular'; @Component({ standalone: true, selector: 'app-basic-editor', imports: [FormsModule, Eddyter], template: ` `, }) export class BasicEditorComponent { apiKey = ''; content = '

'; onReady() {} onAuthSuccess() {} onAuthError(message: string) { console.error(message); } } ``` Import `@import '@eddyter/angular/styles.css';` in `src/styles.css`. Global styles must be in the initial bundle — importing only inside a lazy-loaded chunk is not enough for production builds. ### Svelte — `@eddyter/svelte` (Svelte 5) Runes-based `` component for SvelteKit and standalone Vite apps. ```bash npm install @eddyter/svelte ``` ```svelte (content = html)} onReady={() => console.log('ready')} onAuthSuccess={() => console.log('auth ok')} onAuthError={(err) => console.error(err)} /> ``` The component imports the SDK stylesheet internally; add `import '@eddyter/core/style.css';` only if styles are missing in production. ### Vue — `@eddyter/vue` (Vue 3, Nuxt, Quasar) `` with `v-model` and ref-based instance access. ```bash npm install @eddyter/vue ``` ```vue ``` The component imports the SDK stylesheet automatically; add `import '@eddyter/core/style.css';` in `main.ts` only if styles are missing in production. ### Laravel — `eddyter/laravel` Composer package Official Laravel integration with a Blade component, hidden textarea for form binding, and CDN-loaded SDK. No React, Vite, or Node.js required. ```bash composer require eddyter/laravel php artisan eddyter:install ``` ```env EDDYTER_API_KEY=your-api-key-here ``` ```blade {{-- layouts/app.blade.php --}} @eddyterStyles @yield('content') @eddyterScripts {{-- posts/create.blade.php --}}
@csrf ``` `php artisan eddyter:install --config` also publishes `config/eddyter.php`; add `--force` to overwrite published files. The combined `@eddyterAssets` directive remains available when separate asset placement is not possible. On submit, the hidden textarea syncs with the editor HTML so `$request->input('body')` contains the rich text content. The package transports HTML but does not sanitize it; apply your application's allowlist or sanitizer when content is not fully trusted. For dynamic DOM (Livewire, Turbo, HTMX), call `EddyterLaravel.initAll()` after new HTML is inserted. ### Custom key verification (any framework) Provide a `customVerifyKey` that resolves to `{ success, message }` to validate keys through your own backend instead of the default endpoint. Example (SDK; the same option exists as a prop/input/binding on every wrapper): ```ts init({ container: '#editor', apiKey, customVerifyKey: async (key) => { const res = await fetch('/api/verify-editor-key', { method: 'POST', body: JSON.stringify({ key }), }); const data = await res.json(); return { success: data.valid, message: data.message }; }, }); ``` --- ## Authentication Standard API key verification enables premium features and AI capabilities. ### How It Works 1. Pass your API key to the `apiKey` prop. 2. The editor validates the key against Eddyter's server. 3. Features are enabled based on your subscription plan. 4. `onAuthSuccess` fires when validation succeeds. 5. `onAuthError` fires if validation fails. ### Custom Verification (Optional) Provide your own verification function to validate keys through your backend: ```tsx { const response = await fetch('/api/verify-editor-key', { method: 'POST', body: JSON.stringify({ key }) }); const data = await response.json(); return { success: data.valid, message: data.message }; }} /> ``` --- ## Features A comprehensive toolset for modern content creation, from basic formatting to advanced AI generation. ### Classic Formatting - **Basic Formatting** — Bold, italic, underline, strikethrough, subscript, superscript. - **Text Colors** — Text color and background highlight with color picker. - **Font Controls** — 20+ font families with adjustable font sizes and line height. - **Text Alignment** — Left, center, right, and justify alignment. ### Lists and Structure - **Bulleted Lists** — Custom bullet styles with proper nesting. - **Numbered Lists** — Decimal, alpha, and roman numeral formats. - **Checklists** — Interactive checkboxes with strikethrough. - **Headings** — H1–H6 heading levels. - **Note Panels** — Callout/note blocks for tips, warnings, and info. ### Tables - **Table Operations** — Insert/delete rows and columns, merge cells. - **Cell Resizing** — Drag to resize columns and rows. - **Header Styling** — Distinct header row styling. - **Context Menu** — Right-click menu for quick actions. ### Media Support - **Images** — Drag-drop upload with 8-point resize handles. - **Videos** — YouTube/Vimeo embed with responsive players. - **File Attachments** — Upload and attach downloadable files. - **Link Management** — Insert links with floating editor and preview. ### Collaboration - **Comments** — Inline commenting powered by the `currentUser` prop. - **@Mentions** — Tag users with the `mentionUserList` prop. - **Version History** — Server-backed, plan-gated document versions with save/preview/restore in a panel. Pass a correct per-document `documentId`, and call `saveVersion({ documentId })` from your Save button (required for new records / single-page apps; the panel's own Save button covers existing records). See Version History under API Reference. ### AI Power (Premium) Available on AI Pro (BYOK) and AI Managed plans. - **Smart Chat** — In-editor AI assistant for research, drafting, and creative ideas. - **Smart Autocomplete** — Predictive text suggestions as you type. - **Refinement** — Instantly improve tone, fix grammar, or change content length. - **Gen-AI Images** — Create custom visuals from text prompts inside your document. ### Slash Commands Type `/` at the start of a line to open the quick formatting menu. Provides fast access to headings, lists, tables, images, embeds, and more without reaching for the toolbar. --- ## Configuration Tailor every aspect of the editor to fit your application's specific needs. ### Feature availability (which toolbar buttons / features are enabled) Feature availability is **not** configured in your code. It is controlled by the **plan tied to your license key** and managed from your Eddyter dashboard. `ConfigurableEditorWithAuth` has **no** `config`, `toolbarOptions`, or `floatingMenuOptions` prop — do not pass one. When you set `apiKey`, the editor validates it and loads the enabled features for that plan automatically; any feature the plan doesn't include renders as locked. ```tsx // Correct: pass only the apiKey. Enabled features come from the plan. save(html)} /> ``` To turn features on/off, change the plan's configuration in the Eddyter dashboard — not in your integration code. ### Toolbar Behavior Configure toolbar positioning with the `toolbar` prop. In sticky mode you can set `offset` and `zIndex`. In static mode those values are ignored. ```tsx // Sticky toolbar below a 64px fixed header ``` **Defaults:** `{ mode: "sticky", offset: 20, zIndex: 1000 }` **Note:** The `toolbar` prop is an OBJECT, not an array. ### Editor Scroll Area In static toolbar mode, use `editor.maxHeight` to make only the editor text area scrollable: ```tsx // Static toolbar with scrollable content area ``` ### Responsive Layout — Embedding in Flex / Grid Columns The editor is fully responsive out of the box: it shrinks to whatever width its container gives it, and toolbar buttons that don't fit collapse into a `⋯ More` menu (on touch screens the toolbar scrolls horizontally instead). You do **not** need to add any CSS for the standard case — just render it and it fits. **The one rule when placing it inside your OWN flex or grid layout** (e.g. an editor next to a preview/sidebar panel): the ancestor that holds the editor must be allowed to shrink. By default flex and grid items have `min-width: auto`, which means they refuse to shrink below their content and will push the editor (and its toolbar) wider than the screen — the editor looks like it "overflows" or is "cut off" instead of fitting. Fix it on the **host** container (one line): ```tsx // FLEX: give the column that contains the editor `min-w-0`
{/* ← min-w-0 lets the editor shrink */}
``` ```tsx // GRID: size the editor's track with minmax(0, 1fr), NOT 1fr
``` Plain CSS equivalents: add `min-width: 0` to the flex/grid item that wraps the editor, or use `grid-template-columns: minmax(0, 1fr) 400px` instead of `1fr 400px`. This rule applies at every ancestor level between the editor and the page — if you nest several flex/grid wrappers, each one needs to be allowed to shrink (`min-w-0` on the item, or `minmax(0,1fr)` on the grid track). **Self-diagnosing:** if the editor ever does overflow its container, it logs a `[Eddyter]` warning to the browser console at runtime that names the exact offending ancestor element and the one-line fix. Check the console first. **Note:** this is a standard CSS flexbox/grid behavior (the `min-width: auto` default), not specific to Eddyter — the same rule applies to any wide child (code blocks, tables, charts) placed in a flex/grid column. The editor already carries `min-width: 0`, `overflow-x: clip`, and `contain: inline-size` internally, so it only surfaces when a host wrapper reintroduces the constraint. --- ## Keyboard Shortcuts Speed up your workflow with standard keyboard shortcuts. ### Text Formatting | Action | Shortcut | |---|---| | Bold | Ctrl/Cmd + B | | Italic | Ctrl/Cmd + I | | Underline | Ctrl/Cmd + U | | Insert Link | Ctrl/Cmd + K | ### General | Action | Shortcut | |---|---| | Undo | Ctrl/Cmd + Z | | Redo | Ctrl/Cmd + Y | | Select All | Ctrl/Cmd + A | | Paste | Ctrl/Cmd + V | ### Quick Access | Action | Shortcut | |---|---| | Slash Commands | Type `/` at line start | --- ## API Reference ### `` Provides context and configuration for the editor. Must wrap the editor component to enable all features. | Prop | Type | Required | Default | Description | |---|---|---|---|---| | `children` | `ReactNode` | Yes | — | The wrapped content (your editor component). | | `defaultFontFamilies` | `string[]` | No | Built-in list | Override the list of available font families. Use `defaultEditorConfig.defaultFontFamilies` for the standard set. | | `currentUser` | `CurrentUser` | No | — | User info for the comments feature. Object with `id`, `name`, `email`, and optional `avatar` fields. | | `apiKey` | `string` | No | — | API key for read-only/preview mode contexts. | ### `` The core editor component with built-in subscription verification and AI service integration. | Prop | Type | Required | Default | Description | |---|---|---|---|---| | `apiKey` | `string` | **Yes** | — | Your Eddyter subscription key. | | `initialContent` | `string` | No | `""` | Default HTML content to populate the editor. Use `initialContent`, NOT `value`. | | `onChange` | `(html: string) => void` | No | — | Callback that fires on every content change, returning the current HTML string. | | `mode` | `"edit" \| "preview"` | No | `"edit"` | `"edit"` enables full editing; `"preview"` renders content read-only. | | `toolbar` | `{ mode?: "sticky" \| "static"; offset?: number; zIndex?: number }` | No | `{ mode: "sticky", offset: 20, zIndex: 1000 }` | Toolbar positioning config. This is an OBJECT, not an array. | | `editor` | `{ maxHeight?: number }` | No | — | Editor area config. Use `maxHeight` in static toolbar mode to constrain the scrollable content area. | | `uiFontFamily` | `string` | No | `"Geist"` | Font family for the editor's **UI chrome** (toolbar labels, menus, dialogs, sidebars) so it matches your app's typography. Does NOT change document content — that is chosen in the editor's font picker. Pass a single family name, not a CSS stack. See UI Font under Theming. | | `contentFontFamily` | `string` | No | `"DM Sans"` | Starting font for the **document text**, shown as selected in the font picker. A default, not a lock — the user can pick another font and their choice wins. Applies to newly typed text only. NOT the same as `defaultFontFamilies` (which is the list of fonts offered). See UI Font / Content Font under Theming. | | `mentionUserList` | `string[]` | No | `[]` | List of user names available for @mention autocomplete. | | `onAuthSuccess` | `() => void` | No | — | Fires when API key validation succeeds. | | `onAuthError` | `(error: Error) => void` | No | — | Fires when API key validation fails. | | `customVerifyKey` | `(key: string) => Promise<{ success: boolean; message?: string }>` | No | — | Custom async function to verify the API key through your own backend. | | `documentId` | `string` | No | — | Stable id that scopes version history to this document. Pass your record's primary key. See Version History below. | ### Version History Server-backed, plan-gated document versioning. The editor ships the whole UI — a toolbar toggle and a side panel where users **save, name, preview, and restore** versions — and stores snapshots on Eddyter's backend, keyed by your account and a `documentId`. There is **no automatic/timed autosave**; a version is created only when Save is clicked. **1. The one requirement — a correct per-document `documentId`.** It must uniquely and stably identify each document (pass your record's primary key, reuse it on reopen so history reconnects): ```tsx ``` URL fallback caveat: with no `documentId` the editor derives an id from the page URL (and warns). That's only safe when **every document has its own URL** (one editor per page). In a single-page/master-detail app where many documents share one URL (e.g. `/notes`), the fallback hashes them all to the SAME id and their histories COLLIDE — always pass a real per-record `documentId` there. **2. Two ways a version gets created:** - The panel's built-in **Save** button — works on its own for documents whose correct `documentId` is known at mount (existing records). No extra code. - **`saveVersion()` from your own Save button** — the robust path for real apps. Required for brand-new records (whose id only exists after the first save) and whenever you want your app's Save button to also snapshot. Most integrations should wire this: ```tsx import { useRef } from "react"; import { ConfigurableEditorWithAuth, EditorProvider, type EddyterEditorHandle } from "eddyter"; const editorRef = useRef(null); async function handleSave() { // 1) Persist content to your backend first → get the record id (created here for a NEW record). const saved = await saveToYourBackend({ id: note?.id, content }); // 2) Snapshot a version — pass the id explicitly so a new record's first save // captures version 1. Do it BEFORE unmounting the editor (see gotcha below). try { await editorRef.current?.saveVersion({ documentId: saved.id }); } catch (err) { console.error("Version save failed:", err); // non-fatal } } ``` `saveVersion(opts?)` signature: ```ts saveVersion(opts?: { label?: string; // optional name shown in the history panel documentId?: string; // overrides the prop for this call (e.g. a just-created record's id) }): Promise< | { version: VersionMeta; deduped: boolean; documentId: string } // success | { skipped: true; reason: "unavailable" } // history not active > ``` For a brand-new record whose id doesn't exist at mount, pass it at save time via `saveVersion({ documentId })` so the first save captures the original content as version 1. Notes: - **No autosave / dedup**: no timed snapshots — versions come from the panel's Save button or your `saveVersion()` call. Unchanged content returns `deduped: true` and creates nothing, so it's safe to save repeatedly. - **Restore appends, never destroys**: restoring writes the old version as the new latest and backs up current content. - **Plan-gated**: the toggle/panel appear only when the plan includes version history; otherwise a locked upsell shows. - **Timing gotcha** (`saveVersion()` only): it reads the *mounted* editor. If your Save flow switches to a read-only/preview view, call `saveVersion()` BEFORE unmounting — otherwise it returns `{ skipped: "unavailable" }` and no version is created. ### `CurrentUser` Type ```tsx interface CurrentUser { id: string; // Unique user identifier name: string; // Display name email: string; // User email avatar?: string; // Optional avatar URL } ``` ### Helper Exports | Export | Type | Description | |---|---|---| | `defaultEditorConfig` | `object` | Default values (e.g. `defaultFontFamilies`). Note: feature toggles inside it are set by your plan, not from code. | | `EddyterEditorHandle` | `type` | Ref handle type for ``, exposing `saveVersion()` for version history. | --- ## Code Examples ### Basic Editor ```tsx "use client"; import { ConfigurableEditorWithAuth, EditorProvider } from 'eddyter'; import 'eddyter/style.css'; export default function BasicEditor() { return ( console.log('Ready!')} /> ); } ``` ### Editor with State Management ```tsx "use client"; import { useState } from 'react'; import { ConfigurableEditorWithAuth, EditorProvider } from 'eddyter'; import 'eddyter/style.css'; export default function EditorWithState() { const [content, setContent] = useState('

Start writing...

'); return ( ); } ``` ### Minimal Setup Which features/toolbar buttons are available is decided by the plan tied to your license key (managed in the Eddyter dashboard) — not by props. A minimal integration is just the `apiKey`: ```tsx "use client"; import { ConfigurableEditorWithAuth, EditorProvider } from 'eddyter'; import 'eddyter/style.css'; export default function MinimalEditor() { return ( ); } ``` ### Full-Featured Editor with Comments and Mentions ```tsx "use client"; import { useState } from 'react'; import { ConfigurableEditorWithAuth, EditorProvider, defaultEditorConfig } from 'eddyter'; import 'eddyter/style.css'; export default function FullEditor() { const [content, setContent] = useState(''); const currentUser = { id: 'user-001', name: 'Jane Smith', email: 'jane@example.com', avatar: 'https://example.com/jane.jpg' }; return ( console.log('Editor authenticated!')} onAuthError={(err) => console.error('Auth failed:', err)} /> ); } ``` ### Preview / Read-Only Mode ```tsx "use client"; import { ConfigurableEditorWithAuth, EditorProvider } from 'eddyter'; import 'eddyter/style.css'; export default function PreviewMode({ content }: { content: string }) { return ( ); } ``` --- ## Theming ### CSS Variable Customization The editor exposes CSS variables under the `--cteditorf47ac10b-*` namespace. Apply overrides inside a `.eddyter-scope` selector to customize colors, backgrounds, borders, and more. ```css .eddyter-scope { --cteditorf47ac10b-background: #ffffff; --cteditorf47ac10b-foreground: #1a1a1a; --cteditorf47ac10b-primary: #2563eb; --cteditorf47ac10b-border: #e5e7eb; /* ... additional variables */ } ``` ### UI Font Match the editor's chrome to your app's typography with the `uiFontFamily` prop: ```tsx ``` This changes **UI chrome only** — toolbar labels, dropdown items, dialogs, sidebars, toasts. **Document content is unaffected**; the content font is chosen by the end user in the editor's font-family picker and defaults to DM Sans. Do not use `uiFontFamily` to try to set the document font. **Loading is automatic — you normally do not add a font link yourself:** - A system font (Arial, Georgia, Verdana, Times New Roman, …) is used directly, with no network request. - Any Google Fonts family is fetched automatically, including accented and non-Latin names (e.g. `"Caacupé One"`). - A font your page already declares via `@font-face` (a licensed, Adobe Fonts, or self-hosted font) is detected and used as-is, with no request to Google. **Value rules:** - Pass a **single family name**, not a CSS stack. `uiFontFamily="Poppins"` is correct; `uiFontFamily="Poppins, sans-serif"` is rejected. - CSS generic keywords (`serif`, `sans-serif`, `monospace`, `system-ui`) are not supported — the value is treated as a family name. - An unrecognised or malformed name is ignored and the editor falls back to Geist, so a typo degrades quietly rather than breaking the layout. For fonts outside the editor's built-in list, only the regular weight is fetched, so bold chrome is synthesised by the browser. This is cosmetic and affects nothing functionally. ### Content Font `uiFontFamily` (above) styles the interface. `contentFontFamily` sets the starting font for the **document text** the user writes: ```tsx ``` **Do not confuse the three font props:** - `contentFontFamily` (`string`) — the starting font for document text. - `uiFontFamily` (`string`) — the font of the editor's interface. - `defaultFontFamilies` (`string[]`) — the LIST of fonts offered in the picker. **Behaviour:** - The font shows as the selected value in the toolbar's font picker, and is applied to text as the user types. - It is a **default, not a lock**. The user can pick any other font from the picker and their choice wins. - If the user has **pinned** their own default font (a per-browser preference set from the picker), the pin takes precedence over this prop. - It applies to **newly typed text only**. Content loaded via `initialContent` that carries no font of its own keeps rendering in the editor's base font (DM Sans), so a pre-existing document can display both fonts until its text is rewritten. - Loading and value rules are the same as `uiFontFamily`: system fonts and Google Fonts families are handled automatically, a single family name is required (not a CSS stack), and an unrecognised name is ignored. ### Force Theme If your application supports only one theme: - **Dark-only:** Target `.eddyter-scope` with dark-mode variables. - **Light-only:** Target `.dark .eddyter-scope.dark` with light-mode variables. See the full CSS variable list with default values at https://eddyter.com/docs#theming. --- ## Common Pitfalls | Mistake | Fix | |---|---| | Importing `'eddyter/dist/style.css'` | Use `'eddyter/style.css'` — no `dist/` prefix. | | Using `value` prop for content | Use `initialContent` — there is no `value` prop. | | Passing `toolbar` as an array | `toolbar` is an OBJECT: `{ mode, offset, zIndex }`. | | Missing `"use client"` in Next.js | Add `"use client"` at the top of your editor component file. | | Committing API key to source code | Store in environment variables (e.g. `NEXT_PUBLIC_EDITOR_API_KEY`). | | Wrapping editor without `EditorProvider` | `ConfigurableEditorWithAuth` must be inside ``. | | Editor overflows / is cut off inside a flex or grid column | The host flex/grid item won't shrink (`min-width: auto` default). Add `min-w-0` to the flex item that wraps the editor, or size the grid track with `minmax(0, 1fr)` instead of `1fr`. See "Responsive Layout" above; the editor also logs a `[Eddyter]` console warning naming the exact ancestor to fix. | | `saveVersion()` returns `{ skipped: "unavailable" }` | The editor was unmounted before the call (e.g. you switched to preview/read-only in your Save handler). Call `saveVersion()` while the editor is still mounted, then unmount. Also check the plan includes version history. | | Version history panel not appearing | It's plan-gated — the account's plan must include version history (otherwise a locked upsell shows). The toggle lives in the toolbar (edit mode only). | --- ## Migrating from Legacy Editors Eddyter is designed as a drop-in replacement for legacy WYSIWYG editors. If you're migrating from TinyMCE, CKEditor, Quill, or similar: 1. **Content compatibility:** Eddyter accepts standard HTML via `initialContent`, so existing stored content works out of the box. 2. **Event model:** Replace `onChange` / `onEditorChange` callbacks with Eddyter's `onChange` prop, which returns an HTML string. 3. **Toolbar mapping:** Most toolbar features (bold, italic, tables, lists, links, images) have direct equivalents. Which features are enabled is set by your plan in the Eddyter dashboard, not in code. 4. **AI upgrade:** Unlike legacy editors, AI features (chat, autocomplete, refinement, image generation) are built in — no plugins or third-party integrations required. --- ## Support & Resources - **Documentation** — Full docs: https://eddyter.com/docs - **Live Demo** — Try the editor: https://eddyter.com - **Get API Key** — Manage license: https://www.eddyter.com/user/license-key - **Pricing** — Compare plans: https://eddyter.com/pricing - **Support** — Team assistance: https://www.eddyter.com/support - **Tutorials** — Video walkthroughs: https://eddyter.com/tutorials - **Release Notes** — Shipped updates: https://eddyter.com/release-notes - **npm Package** — https://www.npmjs.com/package/eddyter ### Video Tutorials - **What is Eddyter? Why Developers Are Switching to This AI Editor (2026):** https://youtu.be/oNHBa-DImZc - **Integrate Eddyter in 30 Minutes Using AI Tools (Cursor, Claude, Lovable):** https://youtu.be/5lTjRFjUWgs ### Bug Reports & Support Eddyter's repository is private. Report bugs through the in-product bug reporter or via https://www.eddyter.com/support — not through GitHub. ### License Eddyter is licensed under the MIT License. Security, privacy, and compliance are our core technical principles.