Days
Hours
Minutes
Seconds
x
Skip to content
Froala Documentation

Migrate from CKEditor 5 to Froala

This guide walks you through replacing CKEditor 5 with Froala Editor. Each section maps a CKEditor 5 concept directly to its Froala equivalent, with code examples so you can update your project quickly and confidently.

What you need to know first

Before you touch any code, it helps to understand how the two editors differ structurally. The table below maps the core concepts.

Concept CKEditor 5 Froala
Create the editor ClassicEditor.create(el, config) → Promise new FroalaEditor(selector, options, cb)
Target element DOM node via document.querySelector CSS selector string or DOM node
Enable features Import plugins, list in plugins array Bundled by default; toggle with pluginsEnabled
Toolbar toolbar array of named items toolbarButtons array (with responsive grouping)
Read content editor.getData() editor.html.get()
Write content editor.setData(html) editor.html.set(html)
Listen to changes editor.model.document.on('change:data', cb) events: { contentChanged: cb }
Tear down editor.destroy() → Promise editor.destroy()
Licensing licenseKey ('GPL' or commercial key) key (commercial activation key)

Key mental shift: Froala is synchronous and configuration-driven. You do not assemble a plugin list to get bold, lists, links, images, or tables — they are already bundled. Most of your migration effort goes into translating configuration, not wiring up modules.

Getting Started

Step 1 — Remove CKEditor 5

Remove CKEditor 5 and its dependencies from your project.

Remove via npm

npm uninstall

Remove via CDN

Remove the CKEditor 5 stylesheet and script from your HTML:

<!-- Remove these -->
<link rel="stylesheet" href="https://cdn.ckeditor.com/ckeditor5/48.2.0/ckeditor5.css" />
<script src="https://cdn.ckeditor.com/ckeditor5/48.2.0/ckeditor5.umd.js"></script>

Also remove any ClassicEditor.create(…) initialization code — you will replace it in Step 3.

Step 2 — Add Froala

Option A — CDN

Add the stylesheet to your <head>:

<link href="https://cdn.jsdelivr.net/npm/froala-editor/css/froala_editor.pkgd.min.css" rel="stylesheet" type="text/css" />

Add the script before the closing </body> tag:

<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/froala-editor/js/froala_editor.pkgd.min.js"></script>

The .pkgd ("packaged") bundle includes every plugin — the closest match to a fully featured CKEditor 5 build.

Option B — npm

npm install froala-editor --save
import FroalaEditor from 'froala-editor';
import 'froala-editor/css/froala_editor.pkgd.min.css';
import 'froala-editor/css/froala_style.min.css';

// Or import only the plugins you need:
import 'froala-editor/js/plugins/align.min.js';
import 'froala-editor/js/plugins/lists.min.js';
import 'froala-editor/js/plugins/image.min.js';

Step 3 — Initialize the editor

CKEditor 5 creates an instance asynchronously and resolves a Promise. Froala creates the instance synchronously and accepts an optional callback that runs once initialization finishes.

import { ClassicEditor, Essentials,
  Bold, Italic, Paragraph } from 'ckeditor5';
import 'ckeditor5/ckeditor5.css';

ClassicEditor
  .create(document.querySelector('#editor'), {
    licenseKey: 'GPL',
    plugins: [ Essentials, Bold, Italic, Paragraph ],
    toolbar: [ 'undo', 'redo', '|', 'bold', 'italic' ]
  })
  .then(editor => { console.log('Ready.', editor); })
  .catch(err => console.error(err));
const editor = new FroalaEditor(
  '#editor',
  {
    toolbarButtons: [ 'undo', 'redo', '|', 'bold', 'italic' ]
  },
  function () {
    // Runs once initialized — equivalent to .then()
    console.log('Ready.', this);
  }
);
Key differences: Froala accepts a '#editor' CSS selector string directly — no document.querySelector needed. The third argument is an init callback (no Promise). There is no plugin list for bold, italic, or basic editing — those are bundled.

Complete HTML page

<!doctype html>
<html>
  <head>
    <link href="https://cdn.jsdelivr.net/npm/froala-editor/css/froala_editor.pkgd.min.css"
          rel="stylesheet" />
  </head>
  <body>
    <div id="editor"></div>
    <script src="https://cdn.jsdelivr.net/npm/froala-editor/js/froala_editor.pkgd.min.js"></script>
    <script>
      new FroalaEditor('#editor', {
        toolbarButtons: ['bold', 'italic', 'underline', '|', 'insertLink']
      });
    </script>
  </body>
</html>

Configuration and options

Toolbar

Both editors define the toolbar as an ordered array of named buttons and use '|' as a separator. Button names differ, so the migration is mostly a find-and-replace.

Action CKEditor 5 item Froala button
Bold bold bold
Italic italic italic
Underline underline underline
Headings heading paragraphFormat
Bulleted list bulletedList formatUL
Numbered list numberedList formatOL
Link link insertLink
Image imageUpload insertImage
Table insertTable insertTable
Block quote blockQuote quote
Text alignment alignment alignLeft, alignCenter, alignRight
Font color fontColor textColor
Remove formatting removeFormat clearFormatting
Undo / Redo undo / redo undo / redo

toolbar: [
  'heading', '|',
  'bold', 'italic',
  'link', '|',
  'bulletedList',
  'numberedList',
  'insertTable'
]
toolbarButtons: [
  'paragraphFormat', '|',
  'bold', 'italic',
  'insertLink', '|',
  'formatUL',
  'formatOL',
  'insertTable'
]
Responsive toolbars (Froala bonus): Froala can serve different toolbars per screen size using toolbarButtonsMD, toolbarButtonsSM, and toolbarButtonsXS options. CKEditor 5 does not support this out of the box.

Common options mapping

ClassicEditor.create(el, {
  licenseKey: 'GPL',
  placeholder: 'Write something…',
  language: 'fr',
  initialData: '<p>Welcome!</p>',
});
new FroalaEditor('#editor', {
  key: 'YOUR_LICENSE_KEY',
  placeholderText: 'Write something…',
  language: 'fr',
  // Set initial content via element HTML
  // or editor.html.set() after init
});

Plugins

In CKEditor 5, a feature exists only if you import its plugin and declare it in the plugins array. In Froala, the packaged build already includes all plugins. Control which are active with the pluginsEnabled option.

import {
  ClassicEditor,
  Essentials,
  Bold,
  Italic,
  Link,
  List,
  Image,
  Table,
  Paragraph
} from 'ckeditor5';

ClassicEditor.create(el, {
  licenseKey: 'GPL',
  plugins: [
    Essentials,
    Bold,
    Italic,
    Link,
    List,
    Image,
    Table,
    Paragraph
  ]
});
// .pkgd bundle: all plugins already included.
// Just declare what you want active:
new FroalaEditor('#editor', {
  pluginsEnabled: [
    'link', 'lists',
    'image', 'table'
  ],
  toolbarButtons: [
    'bold', 'italic',
    'insertLink', 'formatUL',
    'insertImage', 'insertTable'
  ]
});

Getting and setting content

// Get content
const html = editor.getData();

// Set content
editor.setData('<p>New content</p>');
// Get content
const html = editor.html.get();

// Set content
editor.html.set('<p>New content</p>');

// Insert at cursor position
editor.html.insert('<b>Inserted</b>');
Rendering content outside the editor: Wrap saved HTML in an element with the fr-view class and include froala_style.min.css to preserve all formatting styles.

Events

CKEditor 5 exposes events through several observable objects. Froala centralizes all events in a single events config object. Inside a Froala event handler, this refers to the editor instance.

ClassicEditor
  .create(el, { licenseKey: 'GPL' })
  .then(editor => {
    editor.model.document.on(
      'change:data',
      () => console.log(editor.getData())
    );

    editor.editing.view.document
      .on(
        'focus',
        () => console.log('focused')
      );

    editor.editing.view.document
      .on(
        'blur',
        () => console.log('blurred')
      );
  });
new FroalaEditor('#editor', {
  events: {
    initialized: function () {
      console.log('ready');
    },
    contentChanged: function () {
      console.log(this.html.get());
    },
    focus: function () {
      console.log('focused');
    },
    blur: function () {
      console.log('blurred');
    }
  }
});
Key difference: CKEditor 5 attaches events via editor.on() calls on multiple observable objects inside a .then() callback. Froala accepts a single events object directly in the config. Inside Froala event callbacks, this is the editor instance — giving you direct API access.

Methods

Read-only mode

// Lock-based read-only
editor.enableReadOnlyMode('my-lock');
editor.disableReadOnlyMode('my-lock');
// Direct toggle
editor.edit.off(); // make read-only
editor.edit.on();  // make editable again

Focus and destroy

// Focus
editor.focus();

// Destroy (returns a Promise)
editor.destroy()
  .then(() => console.log('destroyed'));
// Focus
editor.events.focus();

// Destroy (synchronous)
editor.destroy();

Image upload

ClassicEditor.create(el, {
  licenseKey: 'GPL',
  simpleUpload: {
    uploadUrl: '/upload',
    headers: {
      Authorization: 'Bearer <token>'
    }
  }
});
new FroalaEditor('#editor', {
  imageUploadURL: '/upload',
  imageUploadMethod: 'POST',
  imageMaxSize: 5 * 1024 * 1024,
  imageAllowedTypes: [
    'jpeg',
    'jpg',
    'png',
    'gif'
  ],
  events: {
    'image.error': function (err) {
      console.error(err);
    }
  }
});
Server response format: Froala handles the upload form automatically. Your server endpoint should return { "link": "/path/to/image.jpg" }. See the image upload guide for server-side SDK examples.

Framework integration — React

import { CKEditor } from '@ckeditor/ckeditor5-react';
import {
  ClassicEditor,
  Essentials,
  Bold,
  Italic,
  Paragraph
} from 'ckeditor5';
import 'ckeditor5/ckeditor5.css';

function MyEditor() {
  return (
    <CKEditor
      editor={ClassicEditor}
      config={{
        licenseKey: 'GPL',
        plugins: [Essentials, Bold, Italic, Paragraph],
        toolbar: ['bold', 'italic']
      }}
      data="<p>Hello world!</p>"
      onChange={(e, editor) => console.log(editor.getData())}
    />
  );
}
import { useState } from 'react';
import 'froala-editor/css/froala_style.min.css';
import 'froala-editor/css/froala_editor.pkgd.min.css';
import FroalaEditorComponent from 'react-froala-wysiwyg';

function MyEditor() {
  const [model, setModel] =
    useState('<p>Hello world!</p>');

  return (
    <FroalaEditorComponent
      tag="textarea"
      model={model}
      onModelChange={setModel}
      config={{ toolbarButtons: ['bold', 'italic'] }}
    />
  );
}
npm install react-froala-wysiwyg froala-editor

Feature mapping reference

A quick lookup for the most common APIs you will translate.

What you want CKEditor 5 Froala
Create ClassicEditor.create(el, config) new FroalaEditor(selector, options, cb)
On ready .then(editor => …) init callback or events.initialized
Get HTML editor.getData() editor.html.get()
Set HTML editor.setData(html) editor.html.set(html)
Insert HTML model writer API editor.html.insert(html)
Toolbar toolbar: [...] toolbarButtons: [...]
Placeholder placeholder: '...' placeholderText: '...'
Language language: 'de' language: 'de'
Read-only on enableReadOnlyMode(id) editor.edit.off()
Read-only off disableReadOnlyMode(id) editor.edit.on()
Focus editor.focus() editor.events.focus()
Destroy editor.destroy() → Promise editor.destroy()
Content change model.document.on('change:data', cb) events: { contentChanged: cb }
Image upload URL simpleUpload.uploadUrl imageUploadURL
License licenseKey: 'GPL' key: 'YOUR_KEY'

Next steps

You now have a working Froala editor that mirrors your CKEditor 5 setup. Explore these resources to go further.

Do you think we can improve this article? Let us know.

Ready to dive in? Explore our plans