Psst... need a non-technical introduction to CKEditor 5? Read Crafting Content With CKEditor 5.

CKEditor 5 was introduced in Drupal 9.3 and is now the default WYSIWYG editor in Drupal 10. It’s no mere update on its predecessor. In fact, CKEditor 5 was written completely from scratch. This is good news! Drupal 10 users get a completely overhauled content editing experience as a result. CKEditor 5’s features include a vastly improved user interface and premium collaboration tools. 

But there’s some groundwork to be done first. Due to their considerable differences, you can’t simply reuse CKEditor 4 code with CKEditor 5. Some third-party plugins have already been ported to CKEditor 5. But their implementation is different and you’ll need to rewrite any custom code you developed for CKEditor 4. 

Our developers at Evolving Web created this guide to make the implementation process easier for you. We’ve used the plugin ckeditor5-anchor as an example. Below, you’ll find step-by-step instructions with code for integrating the anchor plugin with Drupal.

Code Examples

The ckeditor5_dev module is shipped with a handy starting template. This is based on the Block Widget plugin from the CKEditor 5 documentation: Implementing a block widget.

Also check out this list of contrib modules that provide CKEditor 5 plugins, such as ckeditor_accordion.

Step 1: Create a Custom Module

Let’s name the module ckeditor5_anchor. The module should not contain any PHP code. Aside from the info.yml file, the module will include the following.

Ckeditor5_anchor.ckeditor5.yml

ckeditor5_anchor_anchor:

  ckeditor5:
    plugins:
      - anchor.Anchor

  drupal:
    label: CKEditor5 Anchor
    library: ckeditor5_anchor/anchor
    admin_library: ckeditor5_anchor/anchor.admin
    toolbar_items:
      Anchor:
        label: Anchor
    elements:
      - <p>

Note that library is the plugin code itself. In our case, admin_library is CSS to be applied on the text format configuration page. CKEditor provides more details about the file structure.

ckeditor5_anchor.libraries.yml

anchor:
  remote: https://github.com/bvedad/ckeditor5-anchor
  version: "1.0.0"
  license:
    name: GNU-GPL-2.0-or-later
    url: https://github.com/ckeditor/ckeditor5/blob/master/LICENSE.md
    gpl-compatible: true
  js:
    js/build/anchor.js: { preprocess: false, minified: true }
  css:
    theme:
      css/anchoractions.css: { }
      css/anchor.css: { }
      css/anchorform.css: { }
      css/anchorimage.css: { }
  dependencies:
    - core/ckeditor5

anchor.admin:
  css:
    theme:
      css/anchor.admin.css: { }

The anchor library contains:

  • js/build/anchor.js which is the plugin code.
  • CSS files are taken from the plugin source. For some reason, CSS files for this particular plugin are actually SCSS files with .css extension. You’ll need to convert them to true .css files.

package.json. 

Copy it from the CKEditor 5 plugin starter template.

{
  "name": "drupal-ckeditor5-anchor",
  "version": "1.0.0",
  "description": "Drupal CKEditor5 Anchor plugin",
  "author": "",
  "license": "GPL-2.0-or-later",
  "scripts": {
    "watch": "webpack --mode development --watch",
    "build": "webpack"
  },
  "devDependencies": {
    "@ckeditor/ckeditor5-dev-utils": "^30.0.0",
    "ckeditor5": "~34.1.0",
    "raw-loader": "^4.0.2",
    "terser-webpack-plugin": "^5.2.0",
    "webpack": "^5.51.1",
    "webpack-cli": "^4.4.0"
  },
  "dependencies": {
    "@ckeditor/ckeditor5-core": "^36.0.1",
    "@ckeditor/ckeditor5-image": "^36.0.1"
  }
}

In our case, we also needed @ckeditor/ckeditor5-core and @ckeditor/ckeditor5-image. Pay attention to the webpack hints when compiling js/build/anchor.js. 

webpack.config.js. 

Copy it from the CKEditor 5 plugin starter template. Both of CKEditor’s documentation examples use webpack, although you may decide to use another tool to pack your javascript code.

Create the following folders:

  • ckeditor5_anchor/js/build for your minified plugin code.
  • ckeditor5_anchor/js/ckeditor5_plugins/anchor for the source JS code.
  • ckeditor5_anchor/css
  • ckeditor5_anchor/icons

Step 2: Get the Source Plugin Code

Clone the plugin source code somewhere outside of your site.

gh repo clonebvedad/ckeditor5-anchor

Copy ./src and ./lang folders into ckeditor5_anchor/js/ckeditor5_plugins/anchor.

Copy CSS and icon files into ckeditor5_anchor/css and ckeditor5_anchor/icons respectively. They’re located in the ./theme folder for the anchor plugin.

Step 3: Install the Dependencies

npm install

Generate js/build/anchor.js.

./node_modules/.bin/webpack --mode development --watch

At this stage, you will likely encounter missing dependencies warnings.

Step 4: Modify the source code

Add ckeditor5_anchor/js/ckeditor5_plugins/anchor/src/index.js

/**
 * @file The build process always expects an index.js file. Anything exported
 * here will be recognized by CKEditor 5 as an available plugin. Multiple
 * plugins can be exported in this one file.
 *
 * I.e. this file's purpose is to make plugin(s) discoverable.
 */

import Anchor from './anchor';

export default {
  Anchor,
};

We need to modify the import section to import all CKEditor libraries from one of the files under ckeditor5/src/ (ckeditor5_achor/node_modules/ckeditor5/src/). For example:

-import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
-import MouseObserver from '@ckeditor/ckeditor5-engine/src/view/observer/mouseobserver';
-import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
-import Command from '@ckeditor/ckeditor5-core/src/command';
-import findAttributeRange from '@ckeditor/ckeditor5-typing/src/utils/findattributerange';
-import toMap from '@ckeditor/ckeditor5-utils/src/tomap';
-import Collection from '@ckeditor/ckeditor5-utils/src/collection';
-import first from '@ckeditor/ckeditor5-utils/src/first';
-import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';

+import { Plugin } from 'ckeditor5/src/core';
+import { MouseObserver } from 'ckeditor5/src/engine';
+import { Clipboard } from 'ckeditor5/src/clipboard';
+import { Command } from 'ckeditor5/src/core';
+import { findAttributeRange } from 'ckeditor5/src/typing';
+import { toMap, Collection, first } from "ckeditor5/src/utils";
+import { ButtonView } from 'ckeditor5/src/ui';

Import icons shipped with the source plugin from the right Drupal module path. 

-import anchorIcon from '../theme/icons/anchor.svg';
+import anchorIcon from '../../../../icons/anchor.svg';

Replace importing all icons loaded from the CKEditor core @ckeditor/ckeditor5-core with import { icons } from ckeditor5/src/core.

-import pencilIcon from '@ckeditor/ckeditor5-core/theme/icons/pencil.svg';
+import { icons } from 'ckeditor5/src/core';

Use the icons variable in the code where an icon is used.

-this.editButtonView = this._createButton( t( 'Edit anchor' ), pencilIcon, 'edit' );
+this.editButtonView = this._createButton( t( 'Edit anchor' ), icons.pencil, 'edit' );

Almost there! One more change is needed specifically for ckeditor5-anchor

 function isTyping( editor ) {
-	const input = editor.plugins.get( 'Input' );
-
-	return input.isInput( editor.model.change( writer => writer.batch ) );
+  const batch = editor.model.change( writer => writer.batch );
+  return batch.isTyping;
 }

Note that the source plugin uses an outdated method. Input.isInput was replaced with batch flags. It’s important to expect these issues and always QA your CKEditor5 plugins after porting them.

Additional Notes

  • anchor.admin library contains a CSS file: ckeditor5_anchor/css/anchor.admin.css. 
/* Icons */
.ckeditor5-toolbar-button-Anchor {
  background-image: url(../icons/anchor.svg);
}

This icon is to be displayed on the text format configuration page.

  • You can migrate CKEditor4 plugin settings to a new CKEditor5 plugin using \Drupal\ckeditor5\Annotation\CKEditor4To5Upgrade. Refer to examples in the core.

Useful Resources

Evolving Web guides

Here are a few more articles we wrote about Drupal's new default editor:

CKEditor Documentation 

We’d recommend reading all of the tutorials available in CKEditor 5 Framework documentation. You can safely skip the editor init part as Drupal already inits the editor for you. 

In particular, read up on Drupal’s slightly different way of importing JS dependencies as well as icons:

Drupal Documentation

Useful pages from drupal.org include:

Your Agency Partner and Go-To Drupal Expert

Evolving Web has been delivering ambitious Drupal projects for 16+ years. Our digital agency was established by two passionate Drupalists and we continue to be deeply involved in the open source community as contributors, thought leaders, sponsors, and event hosts. Our clients trust us to provide expert guidance and support on website audits, digital strategy, design, development, migrations, and more. Partner with us to take your digital platform to the next level.