# 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
console.log('ready')"
@auth-success="() => console.log('auth ok')"
@auth-error="(e) => console.error(e)"
/>
```
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 --}}
```
`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.