New Froala Editor v5.3.1 is here – Learn More
Froala Documentation
- Installation Guides
- Browser Support
- Languages Support
- Shortcuts
- Activation
- Examples
- Customize the Editor
- Use-cases
- Plugins
- APIs
- Development Frameworks
- Server Integrations
- Server SDKs
- Migration Guides
- Changelog
- Tutorials
- Froala Docs
- /
- Migration Guides
- /
- Migrate from Quill to Froala
Migrate from Quill to Froala
Move your project from Quill 2.0.3 to the Froala WYSIWYG Editor v5 with confidence. This guide maps every Quill concept to its Froala equivalent and shows side-by-side code for setup, configuration, content handling, events, image upload, and React.
What you need to know first
Quill and Froala are both DOM-based rich text editors that you attach to an element on the page, but they take different approaches to the document model. Quill stores content as a structured Delta (a JSON description of operations) and renders HTML from it. Froala works directly with HTML as its source of truth. For most teams that already store content as HTML, this makes Froala a more direct fit and removes the Delta-to-HTML conversion layer entirely.
The table below maps the core concepts you will touch during a migration. Quill 2.0.3 is free and BSD-licensed; Froala is a commercial product that requires a license key for production use, which unlocks 30+ plugins, dedicated support, and framework SDKs.
| Concept | Quill 2.0.3 | Froala v5 |
|---|---|---|
| Initialization | new Quill('#editor', options) | new FroalaEditor('#editor', options) |
| Element targeting | CSS selector or DOM node (block element) | CSS selector or DOM node — div, textarea, span, even an img |
| Enabling features | Built-in modules (toolbar, syntax, history…) | 30+ pluginsEnabled plugins |
| Toolbar | modules.toolbar array of format groups | toolbarButtons array of button groups |
| Reading content | getSemanticHTML() / getContents() (Delta) | editor.html.get() |
| Writing content | setContents() / clipboard.dangerouslyPasteHTML() | editor.html.set(html) |
| Events | quill.on('text-change', …) | events: { contentChanged: … } or editor.events.on(…) |
| Teardown | No public destroy — drop the reference / remove the DOM | editor.destroy() |
| Licensing | Free, open source (BSD) | Commercial license key (key option) |
The one mental shift: stop thinking in Deltas. In Quill, content lives as a JSON Delta and HTML is a projection of it. In Froala, HTML is the content — you read it withhtml.get()and write it withhtml.set(). If your backend already stores HTML, you can drop your Delta serialization code during the move.
Swap Quill for Froala in three steps
Getting Froala running in place of Quill takes three short steps: remove the Quill assets, add the Froala assets, and initialize the editor on the same element.
Step 1 — Remove Quill
Start by removing every Quill asset and initialization call from your project. Delete the Quill stylesheet, the Quill library script, and the snippet where you construct the editor.
<!-- Quill stylesheet (remove) -->
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.snow.css" rel="stylesheet" />
<!-- Quill library (remove) -->
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.js"></script>
<!-- Quill init (remove) -->
<script>
const quill = new Quill('#editor', { theme: 'snow' });
</script>
If you installed Quill from npm, also remove it from your dependencies: npm uninstall quill. Leave your editor container element (for example <div id="editor">) in place — Froala will attach to the same element.
Step 2 — Add Froala
Add the Froala CSS and JavaScript. The .pkgd ("packaged") build bundles the editor and all official plugins, so you do not need to load plugin files individually while you get started.
Install via CDN
The two editors load almost identically — one stylesheet and one script. Note that Froala uses the .pkgd.min files.
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.snow.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.js"></script>
<!-- Remove these -->
<link href="https://cdn.jsdelivr.net/npm/froala-editor@latest/css/froala_editor.pkgd.min.css" rel="stylesheet" type="text/css" />
<script type="text/javascript"
src="https://cdn.jsdelivr.net/npm/froala-editor@latest/js/froala_editor.pkgd.min.js"></script>
Install via npm
Prefer a bundler? Install the package and import the editor plus its styles.
// terminal
npm install quill@2.0.3
// module
import Quill from 'quill';
import 'quill/dist/quill.snow.css';
// terminal
npm install froala-editor
// module
import FroalaEditor from 'froala-editor';
import 'froala-editor/css/froala_editor.pkgd.min.css';
// Load a single plugin if you are not using the packaged build:
// import 'froala-editor/js/plugins/align.min.js';
Step 3 — Initialize the editor
Both editors are constructed with new and a selector. The container's existing HTML becomes the editor's initial content in both cases.
<div id="editor">
<p>Hello World!</p>
</div>
<script>
const quill = new Quill('#editor', {
theme: 'snow'
});
</script>
<div id="editor">
<p>Hello World!</p>
</div>
<script>
const editor = new FroalaEditor('#editor');
</script>
Add your license key
For production, pass your commercial key.
new FroalaEditor('#editor', {
key: 'YOUR-FROALA-LICENSE-KEY'
});
Initialize on more element types
Unlike Quill, Froala can attach to elements Quill cannot — including an image (via the image plugin) or only after a click, which is handy for inline-edit interfaces.
// Initialize the editor only when the element is clicked
new FroalaEditor('#editor', { initOnClick: true });
// Attach editing controls directly to an image
new FroalaEditor('img#photo');
Configuration and options
Both editors take an options object as the second argument. Quill nests most behavior under modules; Froala exposes options as flat, top-level keys. Here are the common equivalents.
Common options mapped
const quill = new Quill('#editor', {
theme: 'snow',
placeholder: 'Compose an epic...',
readOnly: false,
modules: {
toolbar: [['bold', 'italic'], ['link', 'image']]
}
});
const editor = new FroalaEditor('#editor', {
placeholderText: 'Compose an epic...',
editorClass: 'my-editor',
toolbarButtons: [
['bold', 'italic'],
['insertLink', 'insertImage']
]
// read-only is set via editor.edit.off() at runtime
});
Building the toolbar
Quill toolbar entries are format names; Froala toolbar entries are button names. Use '|' for a separator within a group and '-' to start a new line. Froala can also define responsive toolbars per screen size with toolbarButtonsMD, toolbarButtonsSM, and toolbarButtonsXS.
const toolbarOptions = [
['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'],
[{ list: 'ordered' }, { list: 'bullet' }],
[{ align: [] }],
['clean']
];
new Quill('#editor', {
theme: 'snow',
modules: { toolbar: toolbarOptions }
});
new FroalaEditor('#editor', {
toolbarButtons: [
['bold', 'italic', 'underline', 'strikeThrough'],
['quote', 'codeView'],
['formatOL', 'formatUL'],
['align'],
['clearFormatting']
]
});
Reference the full option list in the Froala Options docs.
Plugins
Quill extends functionality through modules enabled inside the modules object. Froala uses plugins declared in the pluginsEnabled array. By default Froala enables all bundled plugins, so you only need pluginsEnabled when you want to restrict the set. Remember that when you are not using the .pkgd build, each plugin also needs its own JS/CSS file.
If you want to keep all the default plugins but turn off just a few, use the inverse option, pluginsDisabled, instead of listing everything you want to keep in pluginsEnabled. Any plugin named in pluginsDisabled is removed even if it would otherwise be enabled.
const quill = new Quill('#editor', {
theme: 'snow',
modules: {
syntax: true, // code highlighting
history: { delay: 1000 },
toolbar: [['bold', 'italic'], ['link', 'image']]
}
});
new FroalaEditor('#editor', {
pluginsEnabled: ['image', 'link', 'table', 'codeView'],
toolbarButtons: [
['bold', 'italic'], ['insertLink', 'insertImage']
]
});
// Keep every default plugin except these two
new FroalaEditor('#editor', {
pluginsDisabled: ['video', 'fontFamily']
});
See the full catalog of 30+ plugins and the files each one needs in the Froala Plugins docs. If you do not find what you need, you can build your own custom plugin.
Getting and setting content
This is where the Delta-versus-HTML difference matters most. In Quill you typically work with Deltas, or call getSemanticHTML() to export HTML. In Froala you read and write HTML directly.
Reading content as HTML
// HTML export
const html = quill.getSemanticHTML();
// Or the structured Delta
const delta = quill.getContents();
// Or plain text
const text = quill.getText();
// HTML is the source of truth
const html = editor.html.get();
// Pass false to keep any unsaved/markers stripped:
// const clean = editor.html.get(true);
Writing content
// Set from a Delta
quill.setContents([
{ insert: 'Hello ' },
{ insert: 'World!', attributes: { bold: true } },
{ insert: '\n' }
]);
// Or paste raw HTML
quill.clipboard.dangerouslyPasteHTML(
'<p>Hello <strong>World!</strong></p>'
);
// Set HTML directly
editor.html.set(
'<p>Hello <strong>World!</strong></p>'
);
// Insert at the cursor instead of replacing
editor.html.insert('<span>inserted</span>');
Tip: if your data layer currently round-trips Deltas, you can usually switch it to store thegetSemanticHTML()output during a transition period, then feed that same HTML straight into Froala'shtml.set()— no schema change required.
Events
Quill registers listeners imperatively with quill.on(). Froala lets you declare handlers inside the events object at construction time, or bind them dynamically with editor.events.on(). Inside a Froala handler, this is the editor instance.
Listening for content changes
quill.on('text-change', (delta, oldDelta, source) => {
if (source === 'user') {
console.log('User edited:', quill.getSemanticHTML());
}
});
quill.on('selection-change', (range) => {
if (range) console.log('Cursor at', range.index);
});
new FroalaEditor('#editor', {
events: {
contentChanged: function () {
// `this` is the editor instance
console.log('User edited:', this.html.get());
},
focus: function () {
console.log('Editor focused');
}
}
});
Binding and unbinding dynamically
function handler() {
console.log('changed');
}
quill.on('text-change', handler);
quill.off('text-change', handler);
const editor = new FroalaEditor('#editor', {}, function () {
// runs after initialization
editor.events.on('contentChanged', function () {
console.log('changed');
});
// later: editor.events.off('contentChanged');
});
Browse every event in the Froala Events docs.
Methods
Froala groups methods into namespaces (html, edit, events, selection…), whereas Quill exposes them flat on the instance. Below are the everyday calls you will replace.
Read-only mode
// Disable user editing
quill.enable(false); // or quill.disable()
// Re-enable
quill.enable();
// Turn editing off
editor.edit.off();
// Turn editing back on
editor.edit.on();
Focus, blur, and teardown
quill.focus();
quill.blur();
// Quill has no public destroy(); you
// drop the reference and remove the DOM node.
quill = null;
editor.events.focus();
editor.events.blur();
// Clean teardown that restores the original element
editor.destroy();
The complete method reference lives in the Froala Methods docs.
Image upload
Quill ships a basic image button that inserts a base64 data URL by default; uploading to a server requires a custom handler or a community module. Froala includes a full image upload pipeline out of the box — point it at your endpoint and configure limits and allowed types.
Quill — custom upload handler
const toolbar = quill.getModule('toolbar');
toolbar.addHandler('image', () => {
const input = document.createElement('input');
input.type = 'file';
input.click();
input.onchange = async () => {
const body = new FormData();
body.append('file', input.files[0]);
const res = await fetch('/upload_image', { method: 'POST', body });
const { url } = await res.json();
const range = quill.getSelection(true);
quill.insertEmbed(range.index, 'image', url);
};
});
Froala — built-in upload
new FroalaEditor('#editor', {
imageUploadURL: '/upload_image', // your endpoint
imageUploadMethod: 'POST',
imageUploadParam: 'file', // field name
imageMaxSize: 5 * 1024 * 1024, // 5 MB
imageAllowedTypes: ['jpeg', 'jpg', 'png'],
events: {
'image.uploaded': function (response) {
// Image is on the server, about to be inserted
},
'image.error': function (error, response) {
console.error(error.code, error.message);
}
}
});
Your endpoint should return JSON shaped like { "link": "https://…/image.jpg" }. See the image upload concept guide and the server-side SDKs for ready-made handlers.
Framework integration — React
In React you usually wrap Quill yourself (or pull in react-quill). Froala ships an official component, react-froala-wysiwyg, with two-way binding through model / onModelChange. Install it with npm install react-froala-wysiwyg.
import { useState } from 'react';
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css';
function Editor() {
const [value, setValue] = useState('<p>Hello</p>');
return (
<ReactQuill
theme="snow"
value={value}
onChange={setValue}
/>
);
}
import { useState } from 'react';
import 'froala-editor/css/froala_style.min.css';
import 'froala-editor/css/froala_editor.pkgd.min.css';
import 'froala-editor/js/plugins.pkgd.min.js';
import FroalaEditorComponent from 'react-froala-wysiwyg';
function Editor() {
const [model, setModel] = useState('<p>Hello</p>');
return (
<FroalaEditorComponent
tag="textarea"
model={model}
onModelChange={setModel}
config={{ placeholderText: 'Edit me…' }}
/>
);
}
Next.js / SSR note: Froala depends on the browserwindow, so import the component dynamically with{ ssr: false }(or load it only on the client) to avoid server-render errors.
Angular and Vue have first-party SDKs too — see the framework plugin docs.
Feature mapping reference
A quick lookup for the most common Quill APIs and their Froala equivalents. Keep this handy while you search-and-replace through your codebase.
| Task | Quill 2.0.3 | Froala v5 |
|---|---|---|
| Create instance | new Quill(sel, opts) | new FroalaEditor(sel, opts) |
| Placeholder | placeholder | placeholderText |
| Toolbar | modules.toolbar | toolbarButtons |
| Inline toolbar | theme: 'bubble' | toolbarInline: true |
| Enable a feature | modules.{name} | pluginsEnabled: [...] |
| Get HTML | getSemanticHTML() | html.get() |
| Set HTML | clipboard.dangerouslyPasteHTML() | html.set() |
| Insert HTML | updateContents(delta) | html.insert() |
| Plain text | getText() | el.text() on the element |
| Content change event | 'text-change' | contentChanged |
| Selection change event | 'selection-change' | focus / blur |
| Read-only on | enable(false) | edit.off() |
| Read-only off | enable(true) | edit.on() |
| Focus | focus() | events.focus() |
| Destroy | drop reference | destroy() |
| Image upload | custom handler | imageUploadURL (built-in) |
| React | react-quill (community) | react-froala-wysiwyg (official) |
You're ready to ship
With initialization, content handling, events, and image upload mapped, your Quill project is ready to run on Froala. Explore these references to fine-tune the editor for your app.
Do you think we can improve this article? Let us know.
Whats on this page hide