---
title: "Migrate from Quill to Tiptap"
description: "Learn how to migrate your existing Quill editor to Tiptap. Complete guide covering content migration, setup, and feature mapping."
canonical_url: "https://tiptap.dev/docs/guides/migrate-from-quill"
---

# Migrate from Quill to Tiptap

Learn how to migrate your existing Quill editor to Tiptap. Complete guide covering content migration, setup, and feature mapping.

Tiptap is a popular Quill alternative and migrating from Quill to Tiptap is straightforward. This guide will help you transition smoothly from Quill's Delta format to Tiptap's extension system.

## Content migration

### HTML content compatibility

Quill uses the Delta format for content storage, which needs to be converted to HTML for Tiptap:

```ts
// Convert Quill Delta to HTML first
const quill = new Quill('#temp-editor')
quill.setContents(existingDeltaContent)
const htmlContent = quill.root.innerHTML

// Use HTML content in Tiptap
const editor = new Editor({
  content: htmlContent,
  extensions: [StarterKit],
})
```

If you already have HTML output from Quill, you can use it directly:

```ts
// Your existing Quill HTML content
const existingContent = '<p>Hello <strong>world</strong>!</p>'

// Use directly in Tiptap
const editor = new Editor({
  content: existingContent,
  extensions: [StarterKit],
})
```

While HTML works perfectly, we recommend converting it to Tiptap's JSON format for better performance and readability. For batch conversion of existing content, use the [HTML utility](https://tiptap.dev/docs/editor/api/utilities/html.md) to convert HTML to JSON programmatically.

## Editor setup

### Installation

First, install Tiptap and its dependencies:

```bash
npm install @tiptap/core @tiptap/starter-kit
```

Tiptap supports all modern frontend UI frameworks like React and Vue. Follow the framework-specific installation instructions in our [installation guides](https://tiptap.dev/docs/editor/getting-started/install.md).

### Basic editor setup

Replace your Quill initialization with Tiptap:

```ts
// Quill (before)
const quill = new Quill('#editor', {
  theme: 'snow',
  modules: {
    toolbar: [
      ['bold', 'italic', 'underline'],
      ['link', 'image'],
      [{ list: 'ordered' }, { list: 'bullet' }],
      [{ header: [1, 2, 3, false] }],
    ],
  },
})

// Tiptap (after)
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'

const editor = new Editor({
  element: document.querySelector('#editor'),
  extensions: [StarterKit],
  content: '<p>Hello World!</p>',
})
```

## Extensions

### Understanding Tiptap's extension system

Tiptap uses a modular extension system that resembles Quill's module system. Each feature is an independent extension that can be configured and customized.

The [StarterKit](https://tiptap.dev/docs/editor/extensions/functionality/starterkit.md) is a bundle of all the basic extensions, and you can add or remove other extensions as needed.

Explore all available extensions in our [extensions guide](https://tiptap.dev/docs/editor/extensions/overview.md), or [create your own](https://tiptap.dev/docs/editor/extensions/custom-extensions.md) to support custom functionality and HTML elements.

### Common Quill module equivalents

| Quill Feature         | Tiptap Extension                        | Notes                  |
| --------------------- | --------------------------------------- | ---------------------- |
| Bold                  | `Bold`                                  | Included in StarterKit |
| Italic                | `Italic`                                | Included in StarterKit |
| Underline             | `Underline`                             | Included in StarterKit |
| Link                  | `Link`                                  | Included in StarterKit |
| Image                 | `Image`                                 | Available separately   |
| List (bullet/ordered) | `BulletList`, `OrderedList`, `ListItem` | Included in StarterKit |
| Header                | `Heading`                               | Included in StarterKit |
| Blockquote            | `Blockquote`                            | Included in StarterKit |
| Code Block            | `CodeBlock`                             | Included in StarterKit |
| Strike                | `Strike`                                | Included in StarterKit |
| Color                 | `TextStyle`, `Color`                    | Available separately   |
| Background            | `TextStyle`, `Highlight`                | Available separately   |
| Align                 | `TextAlign`                             | Available separately   |

### Extension configuration

```ts
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import Image from '@tiptap/extension-image'
import TextAlign from '@tiptap/extension-text-align'
import { Color } from '@tiptap/extension-color'
import TextStyle from '@tiptap/extension-text-style'

const editor = new Editor({
  extensions: [
    StarterKit,
    Image.configure({
      inline: true,
      allowBase64: true,
    }),
    TextAlign.configure({
      types: ['heading', 'paragraph'],
    }),
    TextStyle,
    Color.configure({
      types: [TextStyle.name, ListItem.name],
    }),
  ],
})
```

### Custom extensions

For Quill custom modules or blots, create custom Tiptap extensions. See our [custom extensions guide](https://tiptap.dev/docs/editor/extensions/custom-extensions.md) for detailed instructions.

## UI implementation

### Toolbar implementation

Quill's toolbar configuration translates to custom UI components in Tiptap:

```tsx
// Quill toolbar config
toolbar: [
  ['bold', 'italic', 'underline'],
  ['link', 'image'],
  [{ list: 'ordered' }, { list: 'bullet' }],
  [{ header: [1, 2, 3, false] }],
]

// Tiptap equivalent (React example)
function Toolbar({ editor }) {
  if (!editor) return null

  return (
    <div className="toolbar">
      <div className="toolbar-group">
        <button
          onClick={() => editor.chain().focus().toggleBold().run()}
          className={editor.isActive('bold') ? 'active' : ''}
        >
          Bold
        </button>
        <button
          onClick={() => editor.chain().focus().toggleItalic().run()}
          className={editor.isActive('italic') ? 'active' : ''}
        >
          Italic
        </button>
        <button
          onClick={() => editor.chain().focus().toggleUnderline().run()}
          className={editor.isActive('underline') ? 'active' : ''}
        >
          Underline
        </button>
      </div>

      <div className="toolbar-group">
        <button
          onClick={() => {
            const url = window.prompt('URL')
            if (url) {
              editor.chain().focus().extendMarkRange('link').setLink({ href: url }).run()
            }
          }}
          className={editor.isActive('link') ? 'active' : ''}
        >
          Link
        </button>
        <button
          onClick={() => {
            const url = window.prompt('Image URL')
            if (url) {
              editor.chain().focus().setImage({ src: url }).run()
            }
          }}
        >
          Image
        </button>
      </div>

      <div className="toolbar-group">
        <button
          onClick={() => editor.chain().focus().toggleOrderedList().run()}
          className={editor.isActive('orderedList') ? 'active' : ''}
        >
          Numbered List
        </button>
        <button
          onClick={() => editor.chain().focus().toggleBulletList().run()}
          className={editor.isActive('bulletList') ? 'active' : ''}
        >
          Bullet List
        </button>
      </div>

      <div className="toolbar-group">
        <select
          onChange={(e) => {
            const level = parseInt(e.target.value)
            if (level === 0) {
              editor.chain().focus().setParagraph().run()
            } else {
              editor.chain().focus().toggleHeading({ level }).run()
            }
          }}
          value={
            editor.isActive('heading', { level: 1 })
              ? 1
              : editor.isActive('heading', { level: 2 })
                ? 2
                : editor.isActive('heading', { level: 3 })
                  ? 3
                  : 0
          }
        >
          <option value={0}>Normal</option>
          <option value={1}>Heading 1</option>
          <option value={2}>Heading 2</option>
          <option value={3}>Heading 3</option>
        </select>
      </div>
    </div>
  )
}
```

### Pre-built UI components

For faster development, use Tiptap's pre-built UI components:

- Explore our [UI components](https://tiptap.dev/docs/ui-components/getting-started/overview.md) for ready-to-use components
- Check out practical examples in our [default text editor example](https://tiptap.dev/docs/examples/basics/default-text-editor.md)

### Bubble toolbar (Quill's bubble theme)

Replicate Quill's bubble theme using Tiptap's BubbleMenu:

```tsx
import { BubbleMenu, useEditor } from '@tiptap/react'

function MyEditor() {
  const editor = useEditor({
    extensions: [StarterKit],
  })

  return (
    <>
      <EditorContent editor={editor} />
      <BubbleMenu editor={editor}>
        <button
          onClick={() => editor.chain().focus().toggleBold().run()}
          className={editor.isActive('bold') ? 'active' : ''}
        >
          Bold
        </button>
        <button
          onClick={() => editor.chain().focus().toggleItalic().run()}
          className={editor.isActive('italic') ? 'active' : ''}
        >
          Italic
        </button>
        <button
          onClick={() => {
            const url = window.prompt('URL')
            if (url) {
              editor.chain().focus().extendMarkRange('link').setLink({ href: url }).run()
            }
          }}
          className={editor.isActive('link') ? 'active' : ''}
        >
          Link
        </button>
      </BubbleMenu>
    </>
  )
}
```

## Migration checklist

Install Tiptap packages
Convert Quill Delta content to HTML
Replace Quill initialization with Tiptap setup
Map Quill modules to Tiptap extensions
Migrate toolbar configuration to custom UI components
Test content compatibility
Convert existing content to JSON format (recommended)
Implement custom extensions for any missing functionality
Update event handlers and API calls
Test bubble menu functionality if using bubble theme
Verify image upload and handling
Test formatting and styling features

## Next steps

- Explore the [extension overview](https://tiptap.dev/docs/editor/extensions/overview.md) to discover all available extensions
- Learn about [custom extensions](https://tiptap.dev/docs/editor/extensions/custom-extensions.md) for advanced customization
- Check out our [examples](https://tiptap.dev/docs/examples.md) for implementation inspiration
- Review the [performance guide](https://tiptap.dev/docs/guides/performance.md) for optimization tips
