> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/ueberdosis/tiptap/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript Support

> Learn how to use Tiptap with TypeScript for type-safe editor development

# TypeScript Support

Tiptap is written in TypeScript and provides full type safety out of the box. This guide shows you how to leverage TypeScript's features when working with Tiptap.

## Editor Type

The Editor class is fully typed, providing autocomplete and type checking.

<CodeGroup>
  ```typescript Basic Editor Types theme={null}
  import { Editor } from '@tiptap/core'
  import StarterKit from '@tiptap/starter-kit'

  const editor = new Editor({
    extensions: [StarterKit],
    content: '<p>Hello World</p>',
  })

  // TypeScript knows all editor properties and methods
  editor.commands.setContent('<p>New content</p>')
  editor.isEditable // boolean
  editor.state // EditorState
  editor.view // EditorView
  ```
</CodeGroup>

## Extension Options

Define typed options for your extensions.

<CodeGroup>
  ```typescript Extension Options theme={null}
  import { Extension } from '@tiptap/core'

  export interface MyExtensionOptions {
    /**
     * The prefix to use
     * @default 'default'
     */
    prefix: string
    /**
     * Maximum length
     * @default 100
     */
    maxLength: number
    /**
     * Enable feature
     * @default false
     */
    enabled: boolean
  }

  export const MyExtension = Extension.create<MyExtensionOptions>({
    name: 'myExtension',
    
    addOptions() {
      return {
        prefix: 'default',
        maxLength: 100,
        enabled: false,
      }
    },
    
    addCommands() {
      return {
        setPrefix: (prefix: string) => ({ commands }) => {
          // TypeScript knows this.options.prefix exists
          console.log(this.options.prefix)
          return true
        },
      }
    },
  })

  // Usage with type checking
  const editor = new Editor({
    extensions: [
      MyExtension.configure({
        prefix: 'custom', // ✓ Type checked
        maxLength: 200,   // ✓ Type checked
        // invalid: true, // ✗ TypeScript error
      }),
    ],
  })
  ```
</CodeGroup>

Source: `packages/core/src/Extendable.ts:57`

## Node Options and Attributes

Type your node options and attributes.

<CodeGroup>
  ```typescript Node Types theme={null}
  import { Node, mergeAttributes } from '@tiptap/core'
  import type { Node as ProseMirrorNode } from '@tiptap/pm/model'

  export type Level = 1 | 2 | 3 | 4 | 5 | 6

  export interface HeadingOptions {
    levels: Level[]
    HTMLAttributes: Record<string, any>
  }

  export const Heading = Node.create<HeadingOptions>({
    name: 'heading',
    
    addOptions() {
      return {
        levels: [1, 2, 3, 4, 5, 6],
        HTMLAttributes: {},
      }
    },
    
    addAttributes() {
      return {
        level: {
          default: 1,
          rendered: false,
        },
      }
    },
    
    renderHTML({ node, HTMLAttributes }) {
      // TypeScript knows node.attrs.level is a number
      const level: Level = node.attrs.level
      const hasLevel = this.options.levels.includes(level)
      
      return [
        `h${hasLevel ? level : this.options.levels[0]}`,
        mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
        0,
      ]
    },
  })
  ```
</CodeGroup>

Source: `packages/extension-heading/src/heading.ts:6`

## Mark Options and Attributes

Type your mark options and attributes.

<CodeGroup>
  ```typescript Mark Types theme={null}
  import { Mark, mergeAttributes } from '@tiptap/core'
  import type { Mark as ProseMirrorMark } from '@tiptap/pm/model'

  export interface HighlightOptions {
    multicolor: boolean
    HTMLAttributes: Record<string, any>
  }

  export const Highlight = Mark.create<HighlightOptions>({
    name: 'highlight',
    
    addOptions() {
      return {
        multicolor: false,
        HTMLAttributes: {},
      }
    },
    
    addAttributes() {
      if (!this.options.multicolor) {
        return {}
      }
      
      return {
        color: {
          default: null,
          parseHTML: element => 
            element.getAttribute('data-color') || element.style.backgroundColor,
          renderHTML: attributes => {
            if (!attributes.color) {
              return {}
            }
            return {
              'data-color': attributes.color,
              style: `background-color: ${attributes.color}`,
            }
          },
        },
      }
    },
  })
  ```
</CodeGroup>

Source: `packages/extension-highlight/src/highlight.ts:3`

## Command Types

Extend the Commands interface for type-safe commands.

<CodeGroup>
  ```typescript Command Types theme={null}
  import { Extension } from '@tiptap/core'

  // Extend the Commands interface
  declare module '@tiptap/core' {
    interface Commands<ReturnType> {
      myExtension: {
        /**
         * Insert a custom element
         * @example editor.commands.insertCustomElement('foo')
         */
        insertCustomElement: (id: string) => ReturnType
        /**
         * Set custom value
         * @example editor.commands.setCustomValue(42)
         */
        setCustomValue: (value: number) => ReturnType
      }
    }
  }

  export const MyExtension = Extension.create({
    name: 'myExtension',
    
    addCommands() {
      return {
        insertCustomElement: (id: string) => ({ commands }) => {
          return commands.insertContent(`<div data-id="${id}"></div>`)
        },
        setCustomValue: (value: number) => ({ editor }) => {
          console.log(`Setting value to ${value}`)
          return true
        },
      }
    },
  })

  // Now TypeScript knows about these commands
  const editor = new Editor({
    extensions: [MyExtension],
  })

  editor.commands.insertCustomElement('my-id') // ✓ Type safe
  editor.commands.setCustomValue(42)           // ✓ Type safe
  // editor.commands.unknownCommand()          // ✗ TypeScript error
  ```
</CodeGroup>

Source: `packages/extension-bold/src/bold.tsx:13`

## Storage Types

Type your extension storage.

<CodeGroup>
  ```typescript Storage Types theme={null}
  import { Extension } from '@tiptap/core'

  export interface MyExtensionStorage {
    count: number
    items: string[]
    metadata: Record<string, any>
  }

  // Extend the Storage interface
  declare module '@tiptap/core' {
    interface Storage {
      myExtension: MyExtensionStorage
    }
  }

  export const MyExtension = Extension.create<{}, MyExtensionStorage>({
    name: 'myExtension',
    
    addStorage() {
      return {
        count: 0,
        items: [],
        metadata: {},
      }
    },
    
    addCommands() {
      return {
        incrementCount: () => ({ editor }) => {
          // TypeScript knows storage shape
          this.storage.count++
          return true
        },
        addItem: (item: string) => ({ editor }) => {
          this.storage.items.push(item)
          return true
        },
      }
    },
  })

  // Access storage with types
  const count: number = editor.storage.myExtension.count
  const items: string[] = editor.storage.myExtension.items
  ```
</CodeGroup>

Source: `packages/extension-collaboration/src/collaboration.ts:12`

## Editor Options Type

Type your editor configuration.

<CodeGroup>
  ```typescript Editor Options theme={null}
  import { Editor, EditorOptions } from '@tiptap/core'
  import StarterKit from '@tiptap/starter-kit'

  const options: Partial<EditorOptions> = {
    element: document.querySelector('.editor') as HTMLElement,
    extensions: [StarterKit],
    content: '<p>Hello World</p>',
    editable: true,
    autofocus: 'end',
    injectCSS: true,
    
    onCreate: ({ editor }) => {
      console.log('Editor created', editor)
    },
    
    onUpdate: ({ editor, transaction }) => {
      console.log('Content updated', editor.getHTML())
    },
    
    onSelectionUpdate: ({ editor }) => {
      console.log('Selection changed')
    },
    
    onTransaction: ({ editor, transaction }) => {
      console.log('Transaction', transaction)
    },
    
    onFocus: ({ editor, event }) => {
      console.log('Editor focused')
    },
    
    onBlur: ({ editor, event }) => {
      console.log('Editor blurred')
    },
    
    onDestroy: () => {
      console.log('Editor destroyed')
    },
  }

  const editor = new Editor(options)
  ```
</CodeGroup>

Source: `packages/core/src/types.ts:286`

## Generic Types

Use generic types for flexible extensions.

<CodeGroup>
  ```typescript Generic Extension theme={null}
  import { Extension } from '@tiptap/core'

  export interface ListOptions<T> {
    items: T[]
    getLabel: (item: T) => string
    getValue: (item: T) => string | number
  }

  export function createListExtension<T>() {
    return Extension.create<ListOptions<T>>({
      name: 'list',
      
      addOptions() {
        return {
          items: [],
          getLabel: (item: T) => String(item),
          getValue: (item: T) => String(item),
        }
      },
      
      addCommands() {
        return {
          selectItem: (item: T) => ({ editor }) => {
            const label = this.options.getLabel(item)
            const value = this.options.getValue(item)
            console.log(`Selected: ${label} (${value})`)
            return true
          },
        }
      },
    })
  }

  // Usage with specific type
  interface User {
    id: number
    name: string
  }

  const UserList = createListExtension<User>()

  const editor = new Editor({
    extensions: [
      UserList.configure({
        items: [
          { id: 1, name: 'Alice' },
          { id: 2, name: 'Bob' },
        ],
        getLabel: user => user.name,
        getValue: user => user.id,
      }),
    ],
  })
  ```
</CodeGroup>

## React Hook Types

Type-safe React hooks with Tiptap.

<CodeGroup>
  ```typescript React Hooks theme={null}
  import { useEditor, EditorContent } from '@tiptap/react'
  import type { Editor } from '@tiptap/core'
  import StarterKit from '@tiptap/starter-kit'
  import { useCallback } from 'react'

  export default function TypedEditor() {
    const editor: Editor | null = useEditor({
      extensions: [StarterKit],
      content: '<p>Hello World</p>',
    })
    
    // Type-safe command handler
    const setBold = useCallback(() => {
      editor?.chain().focus().toggleBold().run()
    }, [editor])
    
    // Type-safe state checks
    const isBold: boolean = editor?.isActive('bold') ?? false
    const isEditable: boolean = editor?.isEditable ?? true
    
    if (!editor) {
      return null
    }
    
    return (
      <div>
        <button 
          onClick={setBold}
          className={isBold ? 'active' : ''}
        >
          Bold
        </button>
        <EditorContent editor={editor} />
      </div>
    )
  }
  ```
</CodeGroup>

## Utility Types

Tiptap provides useful utility types.

<CodeGroup>
  ```typescript Utility Types theme={null}
  import type {
    Editor,
    Extensions,
    JSONContent,
    Content,
    Command,
    CommandProps,
    NodeViewRenderer,
    MarkViewRenderer,
  } from '@tiptap/core'
  import type { Node as ProseMirrorNode } from '@tiptap/pm/model'
  import type { EditorState, Transaction } from '@tiptap/pm/state'

  // JSON content type
  const content: JSONContent = {
    type: 'doc',
    content: [
      {
        type: 'paragraph',
        content: [
          {
            type: 'text',
            text: 'Hello World',
          },
        ],
      },
    ],
  }

  // Command type
  const myCommand: Command = ({ editor, commands, state, tr }) => {
    return commands.insertContent('Hello')
  }

  // Extension array type
  const extensions: Extensions = [StarterKit]
  ```
</CodeGroup>

## Type Guards

Use type guards for runtime type checking.

<CodeGroup>
  ```typescript Type Guards theme={null}
  import type { Editor } from '@tiptap/core'
  import type { Node as ProseMirrorNode } from '@tiptap/pm/model'

  // Check if node is specific type
  function isHeading(node: ProseMirrorNode): boolean {
    return node.type.name === 'heading'
  }

  function isParagraph(node: ProseMirrorNode): boolean {
    return node.type.name === 'paragraph'
  }

  // Use in code
  const node = editor.state.selection.$anchor.parent

  if (isHeading(node)) {
    const level = node.attrs.level
    console.log(`Current heading level: ${level}`)
  }

  if (isParagraph(node)) {
    console.log('Currently in a paragraph')
  }
  ```
</CodeGroup>

## Strict Mode

Enable strict TypeScript checking.

<CodeGroup>
  ```json tsconfig.json theme={null}
  {
    "compilerOptions": {
      "strict": true,
      "strictNullChecks": true,
      "strictFunctionTypes": true,
      "strictBindCallApply": true,
      "strictPropertyInitialization": true,
      "noImplicitAny": true,
      "noImplicitThis": true,
      "noUnusedLocals": true,
      "noUnusedParameters": true,
      "noImplicitReturns": true,
      "noFallthroughCasesInSwitch": true,
      "esModuleInterop": true,
      "skipLibCheck": true,
      "moduleResolution": "node",
      "resolveJsonModule": true
    }
  }
  ```
</CodeGroup>

## Common Patterns

Useful TypeScript patterns for Tiptap.

<CodeGroup>
  ```typescript Common Patterns theme={null}
  import type { Editor } from '@tiptap/core'
  import type { Node as ProseMirrorNode } from '@tiptap/pm/model'

  // Optional chaining for commands
  editor?.commands.toggleBold()

  // Nullish coalescing for defaults
  const isActive = editor?.isActive('bold') ?? false

  // Type assertion when you know the type
  const element = document.querySelector('.editor') as HTMLElement

  // Non-null assertion (use sparingly)
  const content = editor!.getHTML()

  // Function overloads
  function getContent(editor: Editor): string
  function getContent(editor: Editor, format: 'html'): string
  function getContent(editor: Editor, format: 'json'): JSONContent
  function getContent(
    editor: Editor, 
    format: 'html' | 'json' = 'html'
  ): string | JSONContent {
    return format === 'json' ? editor.getJSON() : editor.getHTML()
  }

  // Discriminated unions
  type Result = 
    | { success: true; data: string }
    | { success: false; error: string }

  function handleResult(result: Result) {
    if (result.success) {
      console.log(result.data)  // TypeScript knows this exists
    } else {
      console.error(result.error)  // TypeScript knows this exists
    }
  }
  ```
</CodeGroup>

<Note>
  Tiptap's TypeScript support helps catch errors at compile time and provides excellent autocomplete in your IDE.
</Note>

## Next Steps

<CardGroup cols={2}>
  <Card title="Custom Extensions" icon="puzzle-piece" href="/guides/custom-extensions">
    Create type-safe custom extensions
  </Card>

  <Card title="Custom Nodes" icon="cube" href="/guides/custom-nodes">
    Build type-safe custom nodes
  </Card>
</CardGroup>
