---
title: Migrate from Quill to Jodit
description: Quill to Jodit migration with before/after code: new Quill vs Jodit.make, Delta to HTML, text-change events, toolbar modules vs buttons, custom handlers and modules vs controls and plugins, image upload, a module map and themes.
keywords: quill to jodit, migrate from quill, quill alternative, replace quill js, quill module equivalent, quill delta to html
---

# Migrate from Quill to Jodit

Quill stores Deltas and renders HTML from them; Jodit stores HTML. The migration starts with a one-time Delta to HTML export, then the editor swap is small: both are initialised with one options object, and most toolbar entries have a direct name in Jodit.

```tldr
Content: export Deltas to HTML once with `getSemanticHTML()`, then store HTML
Init: `new Quill(selector, options)` becomes `Jodit.make(selector, options)`
Toolbar: `modules.toolbar` arrays become a flat `buttons` array
Modules: toolbar handlers become controls, `Quill.register` modules become plugins
Gains: tables, source mode, image upload and Word paste without third-party modules
Compare: [Jodit vs Quill](/jodit/vs/quill/) for features and licenses
```

## 1. Install

````beforeafter
### Quill

```bash
npm install quill react-quill
```

### Jodit

```bash
npm uninstall quill react-quill
npm install jodit
# React projects: npm install jodit-react
# PRO: npm install jodit-pro jodit-pro-react
```
````

Jodit is one package with no peer dependencies; the React, Angular and Vue wrappers are optional. Without a bundler, two tags replace the whole setup:

```html
<link
	rel="stylesheet"
	href="https://cdnjs.cloudflare.com/ajax/libs/jodit/4.2.27/jodit.min.css"
/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jodit/4.2.27/jodit.min.js"></script>
```

## 2. Initialise

````beforeafter
### Quill

```js
import Quill from 'quill';
import 'quill/dist/quill.snow.css';

const quill = new Quill('#editor', {
	theme: 'snow',
	placeholder: 'Start typing',
	modules: {
		toolbar: [
			['bold', 'italic', 'underline'],
			[{ list: 'ordered' }, { list: 'bullet' }],
			['link', 'image'],
			['clean']
		]
	}
});
```

### Jodit

```js
import { Jodit } from 'jodit';
import 'jodit/es2021/jodit.min.css';

const editor = Jodit.make('#editor', {
	placeholder: 'Start typing',
	buttons: ['bold', 'italic', 'underline', '|', 'ol', 'ul', '|', 'link', 'image', '|', 'eraser']
});
```
````

Quill mounts on a `div`; Jodit mounts on a `textarea` or any element and keeps the element's content as the initial value.

## 3. Read and write content, listen to changes

````beforeafter
### Quill

```js
const html = quill.getSemanticHTML();
const delta = quill.getContents();

quill.setContents(delta);
quill.clipboard.dangerouslyPasteHTML('<p>Hello</p>');

quill.on('text-change', (delta, oldDelta, source) => {
	if (source === 'user') save(quill.getSemanticHTML());
});
quill.on('selection-change', range => { if (!range) validate(); });

quill.enable(false);
```

### Jodit

```js
const html = editor.value;

editor.value = '<p>Hello</p>';

editor.events.on('change', newValue => save(newValue));
editor.events.on('blur', () => validate());

editor.setReadOnly(true);
editor.destruct();
```
````

Export existing documents once: run `getSemanticHTML()` in the browser or a server-side Delta renderer, and store the HTML. Jodit `change` fires for user edits; programmatic `editor.value = ...` fires it too, so compare with the previous value if you need the Quill `source` distinction.

## 4. Toolbar

````beforeafter
### Quill

```js
modules: {
	toolbar: [
		[{ header: [1, 2, 3, false] }],
		[{ font: [] }, { size: ['small', false, 'large', 'huge'] }],
		['bold', 'italic', 'underline', 'strike'],
		[{ color: [] }, { background: [] }],
		[{ align: [] }],
		[{ list: 'ordered' }, { list: 'bullet' }, { indent: '-1' }, { indent: '+1' }],
		['link', 'image', 'video'],
		['blockquote', 'code-block'],
		['clean']
	]
}
```

### Jodit

```js
buttons: [
	'paragraph', '|',
	'font', 'fontsize', '|',
	'bold', 'italic', 'underline', 'strikethrough', '|',
	'brush', '|',
	'align', '|',
	'ol', 'ul', 'outdent', 'indent', '|',
	'link', 'image', 'video', '|',
	'source', '|',
	'eraser'
],
toolbarAdaptive: false
```
````

Jodit picks a list by editor width (`buttons`, `buttonsMD`, `buttonsSM`, `buttonsXS`); `toolbarAdaptive: false` keeps one list everywhere. `removeButtons` and `extraButtons` tweak the defaults.

| Quill                                       | Jodit                                                   |
| ------------------------------------------- | ------------------------------------------------------- |
| `{ header: [...] }`                         | `paragraph`                                             |
| `{ font: [] }`, `{ size: [] }`              | `font`, `fontsize`                                      |
| `{ color: [] }`, `{ background: [] }`       | `brush`                                                 |
| `{ align: [] }`                             | `align`                                                 |
| `{ list: 'ordered' }`, `{ list: 'bullet' }` | `ol`, `ul`                                              |
| `{ indent: '-1' }`, `{ indent: '+1' }`      | `outdent`, `indent`                                     |
| `{ script: 'sub' }`, `{ script: 'super' }`  | `subscript`, `superscript`                              |
| `blockquote`                                | `paragraph` dropdown (blockquote entry)                 |
| `code-block`                                | `source` or `paste-code` (PRO) for highlighted snippets |
| `clean`                                     | `eraser`                                                |
| `formula`                                   | no equivalent, use `math` (PRO)                         |

## 5. A custom button

````beforeafter
### Quill

```html
<div id="toolbar">
	<span class="ql-formats">
		<button class="ql-bold"></button>
		<button class="ql-insertDate">Date</button>
	</span>
</div>
<div id="editor"></div>
```

```js
const quill = new Quill('#editor', {
	theme: 'snow',
	modules: {
		toolbar: {
			container: '#toolbar',
			handlers: {
				insertDate() {
					const range = this.quill.getSelection(true);
					this.quill.insertText(range.index, new Date().toDateString(), 'user');
				}
			}
		}
	}
});
```

### Jodit

```js
Jodit.make('#editor', {
	buttons: [
		'bold', '|',
		{
			name: 'insertDate',
			text: 'Date',
			tooltip: 'Insert current date',
			exec(editor) {
				editor.s.insertHTML(new Date().toDateString());
			}
		}
	]
});
```
````

No toolbar markup to write: the control object renders itself with a tooltip, hotkey support and the mobile layout. Register it in `Jodit.defaultOptions.controls` to reuse it; `list` makes a dropdown, `isActive` a toggle. Full guide: [add a custom button](/jodit/pro/docs/how-to/add-custom-button.md).

## 6. Writing a module or plugin

````beforeafter
### Quill

```js
class WordCounter {
	constructor(quill, options) {
		this.el = document.createElement('div');
		quill.container.parentNode.append(this.el);

		quill.on('text-change', () => {
			const words = quill.getText().trim().split(/\s+/).length;
			this.el.textContent = `${words} words`;
		});
	}
}

Quill.register('modules/wordCounter', WordCounter);

new Quill('#editor', { modules: { wordCounter: true } });
```

### Jodit

```js
Jodit.plugins.add('wordCounter', editor => {
	const el = document.createElement('div');

	editor.events
		.on('afterInit', () => editor.container.append(el))
		.on('change', () => {
			const words = editor.editor.textContent.trim().split(/\s+/).length;
			el.textContent = `${words} words`;
		});
});

Jodit.make('#editor');
```
````

Quill formats (Blots) have no counterpart because Jodit has no document model: insert the HTML you need and it stays. Plugins are functions or classes with `init` and `destruct`, enabled for every editor unless listed in `disablePlugins`. See [how to create a plugin](/jodit/pro/docs/how-to/create-plugin.md).

## 7. Image upload

````beforeafter
### Quill

```js
const quill = new Quill('#editor', {
	modules: {
		toolbar: {
			container: [['image']],
			handlers: {
				image() {
					const input = document.createElement('input');
					input.type = 'file';
					input.accept = 'image/*';
					input.onchange = async () => {
						const body = new FormData();
						body.append('file', input.files[0]);
						const { url } = await fetch('/upload', { method: 'POST', body }).then(r => r.json());
						const range = this.quill.getSelection(true);
						this.quill.insertEmbed(range.index, 'image', url, 'user');
					};
					input.click();
				}
			}
		}
	}
});
```

### Jodit

```js
Jodit.make('#editor', {
	uploader: { url: '/upload' }
});
```
````

By default Quill embeds images as base64; Jodit uploads them, with drag and drop and paste handled by the same `uploader` module. It expects `{ "success": true, "data": { "baseurl": "/files/", "files": ["path.jpg"] } }`; adapt another shape in `uploader.process`, or run a connector ([Node.js](/jodit/examples/integration/jodit-nodejs.html), [Python](/jodit/examples/integration/jodit-python.html), PHP) and get the file browser with it. Existing base64 images keep working.

## 8. Module map

| Quill module or community package                   | Jodit                                            | Where            |
| --------------------------------------------------- | ------------------------------------------------ | ---------------- |
| `toolbar`                                           | `buttons` option                                 | core             |
| `history`                                           | `redo-undo`                                      | core             |
| `clipboard`                                         | `paste`, `paste-storage`                         | core             |
| `keyboard` bindings                                 | `commandToHotkeys` option, `hotkeys` plugin      | core             |
| `syntax` (highlight.js)                             | `source` / `paste-code`                          | core / PRO       |
| `quill-better-table`, `quill-table`                 | `table`, `select-cells`, `resize-cells`          | core             |
| `quill-image-uploader`                              | `uploader`                                       | core             |
| `quill-image-resize-module`, `quill-blot-formatter` | `resizer`, `image-properties`                    | core             |
| `quill-magic-url`                                   | `link` (autolink on paste)                       | core             |
| `quill-html-edit-button`                            | `source`                                         | core             |
| `quill-mention`                                     | `autocomplete`                                   | PRO              |
| `quill-emoji`                                       | `emoji`                                          | PRO              |
| `quill-paste-smart`                                 | `paste-from-word`, `clean-html`                  | core             |
| `quill-cursors` with Yjs or ShareDB                 | [Jodit Collaboration](/jodit/pro/collaboration/) | separate product |
| `quill-markdown-shortcuts`                          | no equivalent                                    |                  |

The complete lists: [core plugins](/jodit/docs/modules/plugins.html) and [PRO plugins](/jodit/pro/docs/).

## 9. Themes

````beforeafter
### Quill

```js
import 'quill/dist/quill.snow.css';   // toolbar on top
import 'quill/dist/quill.bubble.css'; // popup on selection

new Quill('#editor', { theme: 'bubble' });
```

### Jodit

```js
Jodit.make('#editor', { theme: 'dark' });

// bubble-style editing: no fixed toolbar, a popup on selection
Jodit.make('#editor', { inline: true, toolbar: false });
```

```html
<!-- or one of ten community themes -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jodit-ui-themes@1/themes/catppuccin/catppuccin.all.min.css" />
<script>Jodit.make('#editor', { theme: 'catppuccin' });</script>
```
````

See [UI themes](/jodit/examples/theme/ui-themes.html) and [dark theme](/jodit/examples/theme/dark.html).

## 10. Option names you will look for

| Quill                        | Jodit                                        |
| ---------------------------- | -------------------------------------------- |
| `placeholder`                | `placeholder`                                |
| `readOnly`                   | `readonly`                                   |
| `bounds`                     | popups are positioned automatically          |
| `formats` allow-list         | `cleanHTML.allowTags`, `cleanHTML.denyTags`  |
| `quill.getText()`            | `editor.text`                                |
| `quill.getLength()`          | `editor.text.length`, `limitChars` for a cap |
| `quill.focus()`              | `editor.s.focus()`                           |
| `quill.getSelection()`       | `editor.s.range`, `editor.s.current()`       |
| `quill.format('bold', true)` | `editor.execCommand('bold')`                 |
| `quill.enable(false)`        | `editor.setReadOnly(true)`                   |

## Checklist

```steps
### Export Deltas to HTML
Run `getSemanticHTML()` or a server-side renderer over existing documents and store the HTML.

### Swap the constructor
`new Quill(selector, options)` becomes `Jodit.make(selector, options)`; rename the toolbar entries with the table above.

### Port handlers and modules
Toolbar handlers become controls; registered modules become `Jodit.plugins.add`.

### Switch images from base64 to uploads
Add `uploader.url`; old base64 images still display.

### Check the output
Open your three most complex documents in the [playground](/jodit/play.html) and compare the HTML.
```

## Links

```cards
- [Jodit vs Quill](/jodit/vs/quill/): Two free editors compared.
- [Options reference](/jodit/docs/options.html): Every Jodit option with defaults.
- [Builder](/jodit/builder/): Pick plugins and buttons and get the config and a smaller bundle.
- [React](/jodit/integrations/react/): jodit-react replaces react-quill.
```
