Apr 15, 2021

Migrating WYSIWYG Editors: From CKEditor 5 to TinyMCE 5

Eugene Kovalov
Eugene Kovalov

Staff Software Engineer

Migrating WYSIWYG Editors

Let’s dive into the migration capabilities of WYSIWYG editors CKEditor 5 and TinyMCE 5.

Choose Your WYSIWYG Editor Library

Why choose one editor library over another? One important reason is licensing—whether you’re able to use the library in proprietary/commercial products or only in free ones.

CKEditor 5 is released under General Public License (GPL), which makes a library available for free programs only; such libraries cannot be used in commercial products unless you purchase an additional license. TinyMCE 5 uses Lesser GPL (LGPL), which permits using the library in commercial products.

The best license for a given library is a matter of strategy. If you’re creating proprietary code for a commercial software, you want LGPL.

WYSIWYG Editor Library ReactJS Integration

Both CKEditor and TinyMCE libraries are distributed as standalone packages (minified JavaScript bundles) with wrappers and integrations for all modern front-end frameworks such as AngularJS, VueJS, and ReactJS. ReactJS support comes through official GitHub accounts—ckeditor5-react and tinymce-react—and in the form of an Editor component with an interface to accommodate initial setup and event handling.

TinyMCE ReactJS Setup

First off, you need to install two npm packages into ReactJS project: tinymce and @tinymce/tinymce-react.

npm install --save tinymce

npm install --save @tinymce/tinymce-react

Then you can use the Editor component in React code by importing …

  1. import { Editor } from '@tinymce/tinymce-react';
  3. import 'tinymce/tinymce'; // core editor functionality
  4. import 'tinymce/icons/default'; // default set of icons
  5. import 'tinymce/themes/silver'; // base editor visual theme
  6. import 'tinymce/plugins/paste'; // paste plugin
  7. import 'tinymce/plugins/link'; // insert link plugin
  8. import 'tinymce/plugins/image'; // insert image url plugin
  9. import 'tinymce/plugins/lists'; // core code for lists - bullet lists, numbered lists

You also need a configuration object that comes as a prop to the Editor component:

  1. {
  2. forced_root_block: 'div',
  3. force_p_newlines: false,
  4. elementpath: false,
  5. branding: false,
  6. min_height: 140,
  7. width: 300,
  8. menubar: false,
  9. plugins: [
  10. 'paste link image lists',
  11. ],
  12. toolbar: 'link bold italic underline bullist numlist image',
  13. content_style: `@import url('https://fonts.googleapis.com/css?family=Lato:regular,bold');
  14. body, td, pre { font-family: Lato,sans-serif; }
  15. `,
  16. }

In the above, you specified an initial set of properties for Editor functionality (list of plugins and toolbar buttons) and look and feel (switches for UI elements, theme, branding, icons and custom CSS) … and onEditorChange callback:

  1. onEditorChange={(content, editor) => {
  2. onBlur(content); // updating the content in the parent state
  3. }}

Use initialValue prop as the initial HTML text value for the editor.

With this initial setup, you should be able to use TinyMCE in a project.

TinyMCE and Webpack: Caveats

When working with modern module bundlers (for example, Rollup and Webpack), you need to make sure all library resources (for example, CSS files and images) used by ReactJS components are included in the output bundle.

CKEditor 5 uses a sophisticated loader-based Webpack configuration, which appears as follows:

  1. // webpack config code
  2. {
  3. test: /ckeditor5-[^/\\]+[/\\]theme[/\\]icons[/\\][^/\\]+\.svg$/,
  4. use: ['raw-loader'],
  5. }, {
  6. test: /ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css$/,
  7. use: [{
  8. loader: 'style-loader',
  9. },
  10. {
  11. loader: 'postcss-loader',
  12. options: styles.getPostCssConfig({
  13. themeImporter: {
  14. themePath: require.resolve('@ckeditor/ckeditor5-theme-lark'),
  15. },
  16. minify: true,
  17. }),
  18. },
  19. ],
  20. }, {
  21. test: /^((?!ckeditor5).)*\.css$/,
  22. use: [{
  23. loader: 'style-loader',
  24. }, {
  25. loader: 'css-loader',
  26. }],
  27. }, {
  28. test: /\.ttf$/,
  29. use: ['file-loader'],
  30. },
  31. // webpack config code

The above uses inline inserts of SVG icons, file urls for TTF font files, style tags for CSS content, and a PostCSS-loader to enable resolution of the templated UI theme (highly customizable ckeditor5-theme-lark).

In contrast, TinyMCE uses local Webpack context. There are no changes to the Webpack configuration but all required theme files (skins) are copied over to the build folder using a file-loader, such as:

  1. require.context(
  2. '!file-loader?name=[path][name].[ext]&context=node_modules/tinymce!tinymce/skins',
  3. true,
  4. /.*/,
  5. );

Important note: Put ! in front of “file-loader” to override any parent Webpack rules that might prevent resource files from loading.

WYSIWYG Editor Plugins: Insert image URL

Here’s the process for implementing custom plugins in CKEditor:

Step 1. Define custom plugin class inherited from Plugin core in @ckeditor/ckeditor5-core/src/plugin

Step 2. Implement init() function that adds custom plugin to the factory and defines execute event:

  1. class InsertImage extends Plugin {
  2. init() {
  3. const { editor } = this;
  4. editor.ui.componentFactory.add('insertImage', (locale) => {
  5. const view = new ButtonView(locale);
  6. view.set({
  7. label: 'Add an image',
  8. icon: imageIcon,
  9. tooltip: true,
  10. });
  11. // Callback executed once the image is clicked.
  12. view.on('execute', () => {
  13. editor.fire('insertImage');
  14. editor.on('insertImageReceived', (event, [imageUrl]) => {
  15. event.off();
  16. editor.model.change((writer) => {
  17. const imageElement = writer.createElement('image', {
  18. src: imageUrl,
  19. });
  20. // Insert the image in the current selection location.
  21. editor.model.insertContent(imageElement, editor.model.document.selection);
  22. editor.fire('insertImageCompleted');
  23. });
  24. });
  25. });
  26. return view;
  27. });
  28. }
  29. }

Step 3. Use the plugin in builtinPlugins collection of defined ClassicEditor wrapper.

The TinyMCE approach, however, is based on IIFE wrapper (not ES6 classes), which requires a PluginManager singleton.

Here’s an example of getting PluginManager from globals:

  1. const global = tinymce.util.Tools.resolve('tinymce.PluginManager');
  2. // later on we need to add plugin to the repository using add
  3. // function
  4. global.add('PLUGIN_NAME', function (editor) {
  5. //adding buttons to the toolbar code goes here
  6. register(editor);
  7. setup(editor);
  8. });

It is time to import the Editor code base and include it in the init object:

  1. import '../customPlugins/PLUGIN_NAME';
  2. // ...
  3. init={{plugins: [
  5. ]}}

After refreshing, you should see the custom plugin button displayed in the toolbar.

WYSIWYG Editor UI Customizations

As previously noted, both libraries have extended support for different themes (TinyMCE calls them skins) and icon sets.

CKEditor uses a PostCSS processor, which requires the custom loader and additional configuration, and significantly improves maintainability of CSS variables and custom code (using mixins).

TinyMCE helps to override the default UI skin by providing Skin Tool, which is quite easy to use. However, any theme template is generic and does not allow sophisticated customizations of full CSS code that defines, for example, how buttons in plugin’s modal dialogs will appear.

To tackle this problem, you need to implement simple CSS overrides that can be supported by a local CSS and/or SaaS-based BEM approach. For example:

  1. .tox-dialog__footer-end .tox-button {
  2. font-size: 13px !important;
  3. background-color: #ef8822 !important;
  4. border-color: #ef8822 !important;
  5. font-family: Lato, sans-serif !important;
  6. text-transform: uppercase !important;
  7. }
  9. .tox-dialog__footer-end .tox-button--secondary {
  10. color: #ef8822 !important;
  11. background-color: transparent !important;
  12. border-color: transparent !important;
  13. }


Migrating from one library to another is never hassle-free. There is no common interface and library legacy code can cause unexpected problems.

TinyMCE is a great example of a simple WYSIWYG editor library for integrations and customizations. Using an extended collection of plugins helps you achieve more with minimal additional code. And its Lesser General Purpose license allows you to use TinyMCE code in commercial products.

To learn more about the technology underpinning [24]7.ai customer engagement products and tools, visit the [24]7 AIVA—Conversational AI Chatbot Technology with NLP web page.

Related Posts

Transform CX with Conversational AI-Powered IVR Technology
Mar 16, 2021 Transform CX with Conversational AI-Powered IVR Technology

Many customers still prefer to pick…

Chatbot Building: Design and Development Process
Feb 22, 2021 Chatbot Building: Design and Development Process

Here’s how the [24]7.ai UX team…