Days
Hours
Minutes
Seconds
x
Skip to content
Froala Documentation

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 with html.get() and write it with html.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 the getSemanticHTML() output during a transition period, then feed that same HTML straight into Froala's html.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 browser window, 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.

Ready to dive in? Explore our plans