Skip to main content

Overview

Yoopta Editor is built with TypeScript and provides comprehensive type definitions for all APIs, plugins, and custom extensions. This guide covers type-safe usage patterns and advanced TypeScript features.

Installation

All Yoopta packages include TypeScript definitions out of the box:
npm install @yoopta/editor @yoopta/paragraph
No additional @types packages are needed.

Core Types

Editor Instance

The main editor type is YooEditor:
import type { YooEditor } from '@yoopta/editor';
import { createYooptaEditor } from '@yoopta/editor';

const editor: YooEditor = createYooptaEditor({
  plugins: PLUGINS,
  marks: MARKS,
});
Key properties:
type YooEditor = {
  // Identification
  id: string;
  readOnly: boolean;
  
  // Content
  children: YooptaContentValue;
  getEditorValue: () => YooptaContentValue;
  setEditorValue: (value: YooptaContentValue) => void;
  isEmpty: () => boolean;
  
  // Block operations
  insertBlock: (options: InsertBlockOptions) => string | undefined;
  updateBlock: (blockId: string, options: UpdateBlockOptions) => void;
  deleteBlock: (blockId: string) => void;
  duplicateBlock: (blockId: string) => void;
  toggleBlock: (blockId: string, options: ToggleBlockOptions) => void;
  moveBlock: (options: MoveBlockOptions) => void;
  focusBlock: (blockId: string, options?: FocusBlockOptions) => void;
  getBlock: (options: GetBlockOptions) => YooptaBlockData | null;
  
  // Element operations
  insertElement: (blockId: string, options: InsertElementOptions) => void;
  updateElement: (blockId: string, options: UpdateElementOptions) => void;
  deleteElement: (blockId: string, options: DeleteElementOptions) => void;
  getElement: (blockId: string, options: GetElementOptions) => SlateElement | null;
  
  // History
  undo: (options?: UndoRedoOptions) => void;
  redo: (options?: UndoRedoOptions) => void;
  
  // Events
  on: <K extends keyof YooptaEventsMap>(
    event: K,
    fn: (payload: YooptaEventsMap[K]) => void
  ) => void;
  emit: <K extends keyof YooptaEventsMap>(
    event: K,
    payload: YooptaEventsMap[K]
  ) => void;
  
  // ... and more
};

Content Types

YooptaContentValue - The editor’s content structure:
type YooptaContentValue = Record<string, YooptaBlockData>;

type YooptaBlockData<T = Descendant | SlateElement> = {
  id: string;
  value: T[];
  type: string; // PascalCase: "Paragraph", "HeadingOne"
  meta: YooptaBlockBaseMeta;
};

type YooptaBlockBaseMeta = {
  order: number;
  depth: number;
  align?: 'left' | 'center' | 'right';
};
SlateElement - Individual elements within blocks:
type SlateElement<K extends string = string, T = any> = {
  id: string;
  type: K; // kebab-case: "paragraph", "heading-one"
  children: Descendant[];
  props?: PluginElementProps<T>;
};

type SlateElementTextNode = {
  text: string;
  bold?: boolean;
  italic?: boolean;
  underline?: boolean;
  code?: boolean;
  strike?: boolean;
  highlight?: any;
};

Plugin Types

Plugin - Type-safe plugin definition:
import type { Plugin, PluginElement, SlateElement } from '@yoopta/editor';

// Define your element types
type ParagraphElement = SlateElement<'paragraph', { nodeType: 'block' }>;

type ElementMap = {
  paragraph: ParagraphElement;
};

const Paragraph: Plugin<ElementMap> = {
  type: 'Paragraph',
  elements: {
    paragraph: {
      render: (props) => <p {...props.attributes}>{props.children}</p>,
      props: { nodeType: 'block' },
    },
  },
  options: {
    display: {
      title: 'Paragraph',
      description: 'Basic text block',
    },
  },
};
Plugin Element Props:
type PluginElementRenderProps = {
  element: SlateElement;
  attributes: Record<string, any>;
  children: React.ReactNode;
  blockId: string;
  HTMLAttributes?: HTMLAttributes<HTMLElement>;
};

Type-Safe Plugin Development

Creating a Custom Plugin

import type { Plugin, SlateElement, YooEditor } from '@yoopta/editor';
import type { RenderElementProps } from 'slate-react';

// 1. Define element types
type CalloutType = 'info' | 'warning' | 'error' | 'success';

type CalloutElement = SlateElement<
  'callout',
  {
    nodeType: 'block';
    theme: CalloutType;
  }
>;

type ElementMap = {
  callout: CalloutElement;
};

// 2. Create typed plugin
const Callout: Plugin<ElementMap, { defaultTheme?: CalloutType }> = {
  type: 'Callout',
  elements: {
    callout: {
      render: (props) => {
        const { element, attributes, children } = props;
        const theme = element.props?.theme || 'info';
        
        return (
          <div {...attributes} data-theme={theme}>
            {children}
          </div>
        );
      },
      props: {
        nodeType: 'block',
        theme: 'info',
      },
    },
  },
  options: {
    defaultTheme: 'info',
  },
};

export default Callout;

Type-Safe Commands

Add custom commands with full type safety:
type CalloutCommands = {
  setTheme: (editor: YooEditor, blockId: string, theme: CalloutType) => void;
  getTheme: (editor: YooEditor, blockId: string) => CalloutType | null;
};

const Callout: Plugin<ElementMap, {}, CalloutCommands> = {
  type: 'Callout',
  elements: { /* ... */ },
  commands: {
    setTheme: (editor, blockId, theme) => {
      editor.updateElement(blockId, {
        type: 'callout',
        props: { theme },
      });
    },
    getTheme: (editor, blockId) => {
      const element = editor.getElement(blockId, { type: 'callout' });
      return element?.props?.theme || null;
    },
  },
};

// Usage
const theme = Callout.commands.getTheme(editor, blockId);
Callout.commands.setTheme(editor, blockId, 'warning');

Type-Safe Event Handling

Events Map

type YooptaEventsMap = {
  change: YooptaEventChangePayload;
  focus: boolean;
  blur: boolean;
  'block:copy': YooptaBlockData;
  'path-change': YooptaPath;
  'decorations:change': undefined;
};

type YooptaEventChangePayload = {
  operations: YooptaOperation[];
  value: YooptaContentValue;
};

Type-Safe Event Listeners

import type { YooptaEventsMap } from '@yoopta/editor';

// Type-safe change handler
editor.on('change', (payload) => {
  // payload is automatically typed as YooptaEventChangePayload
  const { value, operations } = payload;
  console.log('Content changed:', value);
  console.log('Operations:', operations);
});

// Type-safe path change handler
editor.on('path-change', (path) => {
  // path is automatically typed as YooptaPath
  console.log('Current block:', path.current);
  console.log('Selected blocks:', path.selected);
});

Namespace API Types

Blocks Namespace

import { Blocks } from '@yoopta/editor';
import type { GetBlockOptions } from '@yoopta/editor';

// Get block with type-safe options
const options: GetBlockOptions = {
  blockId: 'block-id',
  // or
  at: [0],
  // or
  focus: true,
};

const block = Blocks.getBlock(editor, options);
if (block) {
  // block is typed as YooptaBlockData
  console.log(block.type, block.meta.order);
}

Elements Namespace

import { Elements } from '@yoopta/editor';
import type { UpdateElementOptions } from '@yoopta/editor';

// Update element with type-safe options
const options: UpdateElementOptions = {
  type: 'paragraph',
  props: {
    theme: 'accent',
  },
  children: [
    { text: 'Updated content', bold: true },
  ],
};

Elements.updateElement(editor, blockId, options);

Marks Namespace

import { Marks } from '@yoopta/editor';

// Type-safe mark updates
Marks.update(editor, {
  type: 'highlight',
  value: {
    color: '#ff0000',
    backgroundColor: '#ffff00',
  },
  at: [0, 1, 2], // block indices
});

Generics and Type Parameters

Custom Element Props

type CustomProps = {
  alignment: 'left' | 'center' | 'right';
  color: string;
};

type CustomElement = SlateElement<'custom', CustomProps>;

type ElementMap = {
  custom: CustomElement;
};

const CustomPlugin: Plugin<ElementMap> = {
  type: 'Custom',
  elements: {
    custom: {
      render: (props) => {
        // props.element.props is typed as CustomProps
        const { alignment, color } = props.element.props || {};
        return (
          <div style={{ textAlign: alignment, color }}>
            {props.children}
          </div>
        );
      },
      props: {
        nodeType: 'block',
        alignment: 'left',
        color: '#000000',
      },
    },
  },
};

Plugin Options Type

type MyPluginOptions = {
  maxLength?: number;
  allowedFormats?: string[];
  onValidate?: (value: string) => boolean;
};

const MyPlugin: Plugin<ElementMap, MyPluginOptions> = {
  type: 'MyPlugin',
  elements: { /* ... */ },
  options: {
    display: {
      title: 'My Plugin',
    },
    maxLength: 500,
    allowedFormats: ['bold', 'italic'],
    onValidate: (value) => value.length <= 500,
  },
};

Utility Types

WithoutFirstArg

Used to omit the editor parameter from methods:
import type { WithoutFirstArg } from '@yoopta/editor';

// Original function
function insertBlock(editor: YooEditor, options: InsertBlockOptions): string {
  // ...
}

// Type without first argument
type InsertBlockMethod = WithoutFirstArg<typeof insertBlock>;
// Equivalent to: (options: InsertBlockOptions) => string

Type Guards

Element Type Checking

import type { SlateElement } from '@yoopta/editor';

function isParagraph(element: SlateElement): element is SlateElement<'paragraph'> {
  return element.type === 'paragraph';
}

function isHeading(element: SlateElement): element is SlateElement<'heading-one' | 'heading-two' | 'heading-three'> {
  return ['heading-one', 'heading-two', 'heading-three'].includes(element.type);
}

// Usage
const element = editor.getElement(blockId, { at: [0] });
if (element && isParagraph(element)) {
  // element is typed as SlateElement<'paragraph'>
  console.log('This is a paragraph');
}

Node Type Checking

import { Element, Text } from 'slate';
import type { Descendant } from 'slate';

function processNode(node: Descendant) {
  if (Text.isText(node)) {
    // node is typed as Text
    console.log('Text node:', node.text);
  } else if (Element.isElement(node)) {
    // node is typed as Element
    console.log('Element node:', node.type);
  }
}

React Component Types

Typed YooptaEditor Component

import type { YooEditor } from '@yoopta/editor';
import { YooptaEditor } from '@yoopta/editor';
import type { CSSProperties } from 'react';

type EditorProps = {
  initialValue?: YooptaContentValue;
  onChange?: (value: YooptaContentValue) => void;
  className?: string;
  style?: CSSProperties;
};

function Editor({ initialValue, onChange, className, style }: EditorProps) {
  const editor: YooEditor = useMemo(
    () => createYooptaEditor({
      plugins: PLUGINS,
      marks: MARKS,
      value: initialValue,
    }),
    []
  );
  
  return (
    <YooptaEditor
      editor={editor}
      onChange={onChange}
      className={className}
      style={style}
    >
      {/* ... */}
    </YooptaEditor>
  );
}

Best Practices

Enable strict mode in your tsconfig.json:
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true
  }
}
Use import type for type-only imports:
import type { YooEditor, Plugin } from '@yoopta/editor';
import { createYooptaEditor } from '@yoopta/editor';
Create a dedicated types file for your custom plugins:
// types.ts
export type MyElementMap = {
  custom: CustomElement;
  another: AnotherElement;
};
Let TypeScript infer types when possible:
// Type is inferred from function
editor.on('change', (payload) => {
  // payload type is automatically inferred
});
Always check types at runtime when dealing with dynamic content:
const block = editor.getBlock({ blockId });
if (block && block.type === 'Paragraph') {
  // Safe to access Paragraph-specific properties
}

Common Patterns

Extending Plugin Types

import Paragraph from '@yoopta/paragraph';
import type { Plugin } from '@yoopta/editor';

type ExtendedParagraphProps = {
  indent: number;
  spacing: 'tight' | 'normal' | 'loose';
};

const ExtendedParagraph = Paragraph.extend({
  elements: {
    paragraph: {
      props: {
        nodeType: 'block',
        indent: 0,
        spacing: 'normal',
      } as ExtendedParagraphProps & { nodeType: 'block' },
    },
  },
});

Type-Safe Serialization

import type { YooptaContentValue } from '@yoopta/editor';

function serializeToJSON(value: YooptaContentValue): string {
  return JSON.stringify(value);
}

function deserializeFromJSON(json: string): YooptaContentValue {
  const parsed = JSON.parse(json);
  // Add runtime validation here
  return parsed as YooptaContentValue;
}

Custom Plugins

Create type-safe custom plugins

API Reference

Complete API type definitions

Build docs developers (and LLMs) love