Headless rich text editor built on ProseMirror with extensible architecture for building custom editors
94
The Editor class is the central component of @tiptap/core, managing the editor state, view, extensions, and providing the main API interface for interacting with the rich text editor.
Creates a new editor instance with the specified configuration options.
/**
* Creates a new Tiptap editor instance
* @param options - Configuration options for the editor
*/
constructor(options: Partial<EditorOptions>): Editor;
interface EditorOptions {
/** The DOM element to mount the editor to */
element?: Element;
/** Initial content for the editor */
content?: Content;
/** Array of extensions to load */
extensions?: Extensions;
/** Whether to inject default CSS styles */
injectCSS?: boolean;
/** Initial focus position */
autofocus?: FocusPosition;
/** Whether the editor should be editable */
editable?: boolean;
/** Enable input rules (markdown-like shortcuts) */
enableInputRules?: boolean;
/** Enable paste rules */
enablePasteRules?: boolean;
/** Enable core extensions */
enableCoreExtensions?: boolean;
/** Callback fired before editor creation */
onBeforeCreate?(props: EditorEvents['beforeCreate']): void;
/** Callback fired after editor creation */
onCreate?(props: EditorEvents['create']): void;
/** Callback fired on content updates */
onUpdate?(props: EditorEvents['update']): void;
/** Callback fired on selection updates */
onSelectionUpdate?(props: EditorEvents['selectionUpdate']): void;
/** Callback fired on transactions */
onTransaction?(props: EditorEvents['transaction']): void;
/** Callback fired on focus */
onFocus?(props: EditorEvents['focus']): void;
/** Callback fired on blur */
onBlur?(props: EditorEvents['blur']): void;
/** Callback fired when editor is destroyed */
onDestroy?(): void;
}
type Content =
| string
| JSONContent
| JSONContent[]
| ProseMirrorNode
| null;
type FocusPosition =
| boolean
| 'start'
| 'end'
| 'all'
| number
| { from: number; to?: number };Usage Examples:
import { Editor } from '@tiptap/core';
// Basic editor
const editor = new Editor({
element: document.querySelector('#editor'),
content: '<p>Hello World!</p>'
});
// Editor with callbacks
const editor = new Editor({
element: document.querySelector('#editor'),
content: '<p>Hello World!</p>',
autofocus: 'end',
editable: true,
onUpdate: ({ editor }) => {
console.log('Content updated:', editor.getHTML());
},
onFocus: ({ editor, event }) => {
console.log('Editor focused');
}
});Essential properties for inspecting the editor state and configuration.
/** ProseMirror EditorView instance (proxied when unmounted) */
readonly view: EditorView;
/** Current ProseMirror EditorState */
readonly state: EditorState;
/** ProseMirror Schema built from extensions */
readonly schema: Schema;
/** Storage object shared between extensions */
readonly storage: Storage;
/** Whether the editor accepts input */
readonly isEditable: boolean;
/** Whether the editor currently has focus */
readonly isFocused: boolean;
/** Whether the editor content is empty */
readonly isEmpty: boolean;
/** Whether the editor has been destroyed */
readonly isDestroyed: boolean;
/** Whether the editor is fully initialized */
readonly isInitialized: boolean;Methods for managing the editor lifecycle - mounting, unmounting, and destroying.
/**
* Mount the editor to a DOM element
* @param element - Optional DOM element to mount to (uses constructor element if not provided)
* @returns The editor instance for chaining
*/
mount(element?: Element): Editor;
/**
* Unmount the editor from DOM while preserving the instance for potential remounting
* @returns The editor instance for chaining
*/
unmount(): Editor;
/**
* Permanently destroy the editor instance and clean up resources
*/
destroy(): void;Usage Examples:
// Create editor without mounting
const editor = new Editor({
content: '<p>Hello World!</p>'
});
// Mount later
editor.mount(document.querySelector('#editor'));
// Temporarily unmount (can be remounted)
editor.unmount();
// Mount to different element
editor.mount(document.querySelector('#another-editor'));
// Permanently destroy
editor.destroy();Core command execution interface providing single commands, command chains, and command validation.
/** Access to all registered commands for single execution */
readonly commands: SingleCommands;
/**
* Create a command chain for executing multiple commands in sequence
* @returns ChainedCommands instance
*/
chain(): ChainedCommands;
/**
* Create a command validation interface
* @returns CanCommands instance for testing command executability
*/
can(): CanCommands;
interface SingleCommands {
[commandName: string]: (attributes?: Record<string, any>) => boolean;
}
interface ChainedCommands {
[commandName: string]: (attributes?: Record<string, any>) => ChainedCommands;
/** Execute the command chain */
run(): boolean;
}
interface CanCommands {
[commandName: string]: (attributes?: Record<string, any>) => boolean;
}Usage Examples:
// Single command execution
const success = editor.commands.setBold();
// Command chaining
editor
.chain()
.focus()
.setBold()
.setItalic()
.insertContent('Bold and italic text')
.run();
// Command validation
if (editor.can().setBold()) {
editor.commands.setBold();
}
// Conditional chaining
editor
.chain()
.focus()
.toggleBold()
.insertContent(editor.can().setItalic() ? 'Can italicize' : 'Cannot italicize')
.run();Methods for retrieving and setting editor content in various formats.
/**
* Get editor content as HTML string
* @returns HTML representation of the document
*/
getHTML(): string;
/**
* Get editor content as JSON
* @returns JSONContent representation of the document
*/
getJSON(): JSONContent;
/**
* Get editor content as plain text
* @param options - Text extraction options
* @returns Plain text content
*/
getText(options?: GetTextOptions): string;
interface GetTextOptions {
/** Include block separators (default: true) */
blockSeparator?: string;
/** Include text between given positions */
from?: number;
to?: number;
}
interface JSONContent {
type?: string;
attrs?: Record<string, any>;
content?: JSONContent[];
marks?: Array<{
type: string;
attrs?: Record<string, any>;
}>;
text?: string;
}Usage Examples:
// Get content in different formats
const htmlContent = editor.getHTML();
// '<p><strong>Bold text</strong></p>'
const jsonContent = editor.getJSON();
// { type: 'doc', content: [{ type: 'paragraph', content: [...] }] }
const plainText = editor.getText();
// 'Bold text'
const textWithCustomSeparator = editor.getText({
blockSeparator: ' | '
});
// Get text from specific range
const rangeText = editor.getText({
from: 0,
to: 10
});Methods for updating editor configuration and behavior at runtime.
/**
* Update editor options
* @param options - Partial options to update
*/
setOptions(options: Partial<EditorOptions>): void;
/**
* Change the editable state of the editor
* @param editable - Whether the editor should be editable
*/
setEditable(editable: boolean): void;
/**
* Register a new ProseMirror plugin
* @param plugin - ProseMirror plugin to register
* @param handlePlugins - Optional function to handle plugin conflicts
*/
registerPlugin(
plugin: Plugin,
handlePlugins?: (newPlugin: Plugin, existingPlugins: Plugin[]) => Plugin[]
): void;
/**
* Unregister a ProseMirror plugin
* @param nameOrKey - Plugin name or PluginKey to remove
*/
unregisterPlugin(nameOrKey: string | PluginKey): void;Usage Examples:
// Update editor options
editor.setOptions({
editable: false,
onUpdate: ({ editor }) => {
console.log('New update handler');
}
});
// Toggle editable state
editor.setEditable(false); // Make read-only
editor.setEditable(true); // Make editable again
// Register custom plugin
import { Plugin, PluginKey } from '@tiptap/pm/state';
const customPluginKey = new PluginKey('customPlugin');
const customPlugin = new Plugin({
key: customPluginKey,
// plugin implementation
});
editor.registerPlugin(customPlugin);
// Unregister plugin
editor.unregisterPlugin('customPlugin');
// or
editor.unregisterPlugin(customPluginKey);Methods for inspecting the current editor state and selection.
/**
* Get attributes of the current selection
* @param nameOrType - Node/mark name, NodeType, or MarkType
* @returns Attributes object
*/
getAttributes(nameOrType: string | NodeType | MarkType): Record<string, any>;
/**
* Check if a node or mark is active in the current selection
* @param name - Node or mark name to check
* @param attributes - Optional attributes to match
* @returns Whether the node/mark is active
*/
isActive(name: string, attributes?: Record<string, any>): boolean;Usage Examples:
// Check if formatting is active
const isBold = editor.isActive('bold');
const isItalic = editor.isActive('italic');
const isHeading = editor.isActive('heading', { level: 1 });
// Get current attributes
const linkAttrs = editor.getAttributes('link');
// { href: 'https://example.com', target: '_blank' }
const headingAttrs = editor.getAttributes('heading');
// { level: 2 }
// Use in UI state
function BoldButton() {
const isActive = editor.isActive('bold');
return (
<button
className={isActive ? 'active' : ''}
onClick={() => editor.chain().focus().toggleBold().run()}
>
Bold
</button>
);
}Advanced methods for querying and navigating the document structure.
/**
* Create a NodePos instance at the specified position
* @param pos - Document position
* @returns NodePos for advanced position operations
*/
$pos(pos: number): NodePos;
/**
* Find a single node matching the selector and attributes
* @param selector - Node type selector
* @param attributes - Optional attributes to match
* @returns NodePos if found, null otherwise
*/
$node(selector: string, attributes?: Record<string, any>): NodePos | null;
/**
* Find all nodes matching the selector and attributes
* @param selector - Node type selector
* @param attributes - Optional attributes to match
* @returns Array of NodePos instances
*/
$nodes(selector: string, attributes?: Record<string, any>): NodePos[];Usage Examples:
// Get position info
const pos = editor.$pos(10);
console.log(pos.node()); // Node at position
console.log(pos.parent); // Parent node
// Find specific nodes
const firstHeading = editor.$node('heading');
const h1 = editor.$node('heading', { level: 1 });
// Find all nodes of a type
const allParagraphs = editor.$nodes('paragraph');
const allImages = editor.$nodes('image');
// Advanced querying
const allH2WithClass = editor.$nodes('heading', {
level: 2,
class: 'special'
});
// Navigate and modify
const heading = editor.$node('heading');
if (heading) {
heading.content = 'Updated heading content';
heading.setAttribute({ level: 2 });
}The NodePos class provides a DOM-like interface for navigating and manipulating nodes in the document. It offers powerful querying and modification capabilities similar to browser DOM APIs.
/**
* Represents a position in the document with DOM-like navigation and manipulation capabilities
*/
class NodePos {
/** The ProseMirror node at this position */
readonly node: Node;
/** The document position */
readonly pos: number;
/** The depth in the document tree */
readonly depth: number;
/** Node attributes */
readonly attributes: Record<string, any>;
/** Node text content */
readonly textContent: string;
/** Node size in the document */
readonly size: number;
/** Start position of the node */
readonly from: number;
/** End position of the node */
readonly to: number;
/** Node range (from/to positions) */
readonly range: Range;
/** Corresponding DOM element */
readonly element: HTMLElement;
/** Node content fragment */
content: Fragment;
// Navigation properties
/** Parent node (null if at root) */
readonly parent: NodePos | null;
/** Previous sibling node */
readonly before: NodePos | null;
/** Next sibling node */
readonly after: NodePos | null;
/** Child nodes array */
readonly children: NodePos[];
/** First child node */
readonly firstChild: NodePos | null;
/** Last child node */
readonly lastChild: NodePos | null;
// Query methods
/**
* Find the closest ancestor matching the selector
* @param selector - Node type to search for
* @param attributes - Optional attributes to match
* @returns Closest matching ancestor or null
*/
closest(selector: string, attributes?: Record<string, any>): NodePos | null;
/**
* Find first descendant matching the selector
* @param selector - Node type to search for
* @param attributes - Optional attributes to match
* @returns First matching descendant or null
*/
querySelector(selector: string, attributes?: Record<string, any>): NodePos | null;
/**
* Find all descendants matching the selector
* @param selector - Node type to search for
* @param attributes - Optional attributes to match
* @returns Array of matching descendants
*/
querySelectorAll(selector: string, attributes?: Record<string, any>): NodePos[];
// Modification methods
/**
* Set attributes on this node
* @param attributes - Attributes to set
*/
setAttribute(attributes: Record<string, any>): void;
}
interface Range {
from: number;
to: number;
}Usage Examples:
// DOM-like navigation
const pos = editor.$pos(50);
const parent = pos.parent;
const children = pos.children;
const firstChild = pos.firstChild;
// Query for specific nodes
const heading = pos.querySelector('heading');
const allParagraphs = pos.querySelectorAll('paragraph');
const h1WithId = pos.querySelector('heading', { level: 1, id: 'main' });
// Navigate relationships
const nextHeading = pos.closest('heading')?.after?.querySelector('heading');
// Content manipulation
const paragraph = editor.$node('paragraph');
if (paragraph) {
paragraph.content = 'New paragraph content';
paragraph.setAttribute({ class: 'highlighted' });
}
// Range operations
const range = paragraph?.range;
if (range) {
editor.commands.setTextSelection(range);
}
// Complex navigation
const headings = editor.$nodes('heading');
headings.forEach(heading => {
const level = heading.attributes.level;
console.log(`H${level}: ${heading.textContent}`);
// Find all paragraphs under this heading
const paragraphs = heading.querySelectorAll('paragraph');
console.log(`Contains ${paragraphs.length} paragraphs`);
});
// Tree traversal
function walkNodes(nodePos: NodePos, callback: (node: NodePos) => void) {
callback(nodePos);
nodePos.children.forEach(child => walkNodes(child, callback));
}
walkNodes(editor.$pos(0), node => {
console.log(`${node.node.type.name} at ${node.pos}`);
});Install with Tessl CLI
npx tessl i tessl/npm-tiptap--coredocs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10