CKEditor 5 architecture

Same name in other branches
  1. 10 core/modules/ckeditor5/ckeditor5.api.php \ckeditor5_architecture
  2. 11.x core/modules/ckeditor5/ckeditor5.api.php \ckeditor5_architecture

Overview

The CKEditor 5 module integrates CKEditor 5 with Drupal's filtering and text editor APIs.

Where possible, it uses upstream CKEditor plugins, but it also relies on Drupal-specific CKEditor plugins to ensure a consistent user experience.

Data models

Drupal and CKEditor 5 have very different data models.

Drupal stores blobs of HTML that remains manageable thanks to the use of filters and granular HTML restrictions — crucially this remains manageable thanks to those restrictions but also because Drupal does not need to process, render, understand or otherwise interact with it.

On the other hand, CKEditor 5 must not only be able to render these blobs, but also allow editing and creating it. This requires a much deeper understanding of that HTML.

CKEditor 5 (in contrast with CKEditor 4) therefore has its own data model to represent this information — that data model is explicitly not HTML.

Therefore all interactions between Drupal and CKEditor 5 need to translate between these different data models.

CKEditor 5 Plugins

CKEditor 5 plugins may use either YAML or a PHP annotation for their definitions. A PHP class does not need an annotation if it is defined in yml.

To be discovered, YAML definition files must be named {module_name}.ckeditor5.yml.

The minimally required metadata: the CKEditor 5 plugins to load, the label and the HTML elements it can generate — here's an example for a module providing a Marquee plugin, both in yml or Annotation form:

Declared in the yml file:


# In the MODULE_NAME.ckeditor5.yml file.

MODULE_NAME_marquee:
  ckeditor5:
    plugins: [PACKAGE.CLASS]
  drupal:
    label: Marquee
    library: MODULE_NAME/ckeditor5.marquee
    elements:
      - <marquee>
      - <marquee behavior>

Declared as an Annotation:


# In a scr/Plugin/CKEditor5Plugin/Marquee.php file.

 * @CKEditor5Plugin(
 *   id = "MODULE_NAME_marquee",
 *   ckeditor5 = @CKEditor5AspectsOfCKEditor5Plugin(
 *     plugins = { "PACKAGE.CLASS" },
 *   ),
 *   drupal = @DrupalAspectsOfCKEditor5Plugin(
 *     label = @Translation("Marquee"),
 *     library = "MODULE_NAME/ckeditor5.marquee"
 *     elements = { "<marquee>", "<marquee behavior>" },
 *   )
 * )
 * /

The metadata relating strictly to the CKEditor 5 plugin's JS code is stored in the 'ckeditor5' key; all other metadata is stored in the 'drupal' key.

If the plugin has a dependency on another module, adding the 'provider' key will prevent the plugin from being loaded if that module is not installed.

All of these can be defined in YAML or annotations. A given plugin should choose one or the other, as a definition can't parse both at once.

Overview of all available plugin definition properties:

  • provider: Allows a plugin to have a dependency on another module. If it has a value, a module with a machine name matching that value must be installed for the configured plugin to load.
  • ckeditor5.plugins: A list CKEditor 5 JavaScript plugins to load, as '{package.Class}' , such as 'drupalMedia.DrupalMedia'.
  • ckeditor5.config: A keyed array of additional values for the constructor of the CKEditor 5 JavaScript plugins being loaded. i.e. this becomes the CKEditor 5 plugin configuration settings (see https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/co…) for a given plugin.
  • drupal.label: Human-readable name of the CKEditor 5 plugin.
  • drupal.library: A Drupal asset library to load with the plugin.
  • drupal.admin_library: A Drupal asset library that will load in the text format admin UI when the plugin is available.
  • drupal.class: Optional PHP class that makes it possible for the plugin to provide dynamic values, or a configuration UI. The value should be formatted as '\Drupal\{module_name}\Plugin\CKEditor5Plugin\{class_name}' to make it discoverable.
  • drupal.elements: A list of elements and attributes the plugin allows use of within CKEditor 5. This uses the same syntax as the 'filter_html' plugin with an additional special keyword: '<$text-container>' . Using '<$text-container [attribute(s)]>` will permit the provided attributes in all CKEditor 5's `$block` text container tags that are explicitly enabled in any plugin. i.e. if only '<p>', '<h3>' and '<h2>' tags are allowed, then '<$text-container data-something>' will allow the 'data-something' attribute for '<p>', '<h3>' and '<h2>' tags. Note that while the syntax is the same, some extra nuance is needed: although this syntax can be used to create an attribute on an element, f.e. (['<marquee behavior>']) creating the `behavior` attribute on `<marquee>`, the tag itself must be creatable as well (['<marquee>']). If a plugin wants the tag and attribute to be created, list both: (['<marquee>', '<marquee behavior>']). Validation logic ensures that a plugin supporting only the creation of attributes cannot be enabled if the tag cannot be created via itself or through another CKEditor 5 plugin.
  • drupal.toolbar_items: List of toolbar items the plugin provides. Keyed by a machine name and the value being a pair defining the label:

  toolbar_items:
    indent:
      label: Indent
    outdent:
      label: Outdent
  @encode
- drupal.conditions: Conditions required for the plugin to load (other than
  module dependencies, which are defined by the 'provider' property).
  Conditions can check for five different things:
  - 'toolbarItem': a toolbar item that must be enabled
  - 'filter': a filter that must be enabled
  - 'imageUploadStatus': TRUE if image upload must be enabled, FALSE if it
     must not be enabled
  - 'requiresConfiguration': a subset of the configuration for this plugin
     that must match (exactly)
  - 'plugins': a list of CKEditor 5 Drupal plugin IDs that must be enabled
  Plugins requiring more complex conditions, such as requiring multiple
  toolbar items or multiple filters, have not yet been identified. If this
  need arises, see
  https://www.drupal.org/docs/drupal-apis/ckeditor-5-api/overview#conditions.

All of these can be defined in YAML or annotations. A given plugin should
choose one or the other, as a definition can't parse both at once.

If the CKEditor 5 plugin contains translation they can be automatically
loaded by Drupal by adding the dependency to the core/ckeditor5.translations
library to the CKEditor 5 plugin library definition:

@code
# In the MODULE_NAME.libraries.yml file.

marquee:
 js:
   assets/ckeditor5/marquee/marquee.js: { minified: true }
 dependencies:
   - core/ckeditor5
   - core/ckeditor5.translations

The translations for CKEditor 5 are located in a translations/ subdirectory, Drupal will load the corresponding translation when necessary, located in assets/ckeditor5/marquee/translations/* in this example.

Upgrade path

Modules can provide upgrade paths similar to the built-in upgrade path for Drupal core's CKEditor 4 to CKEditor 5, by providing a CKEditor4To5Upgrade plugin. This plugin type allows:

  • mapping a CKEditor 4 button to an equivalent CKEditor 5 toolbar item
  • mapping CKEditor 4 plugin settings to equivalent CKEditor 5 plugin configuration.

The supported CKEditor 4 buttons and/or CKEditor 4 plugin settings must be specified in the annotation. See Drupal core's implementation for an example.

Public API

The CKEditor 5 module provides no public API, other than:

See also

https://ckeditor.com/ckeditor-5/

\Drupal\text\Plugin\Field\FieldType\TextItemBase

\Drupal\filter\Plugin\Filter\FilterInterface::getHTMLRestrictions()

https://ckeditor.com/docs/ckeditor5/latest/framework/guides/architectur…

ckeditor5.ckeditor5.yml for many examples of CKEditor 5 plugin configuration as YAML.

\Drupal\ckeditor5\Annotation\CKEditor5Plugin

\Drupal\ckeditor5\Annotation\CKEditor5AspectsOfCKEditor5Plugin

\Drupal\ckeditor5\Annotation\DrupalAspectsOfCKEditor5Plugin

\Drupal\ckeditor5\Annotation\CKEditor4To5Upgrade

\Drupal\ckeditor5\Plugin\CKEditor4To5UpgradePluginInterface

\Drupal\ckeditor5\Plugin\CKEditor4To5Upgrade\Core

File

core/modules/ckeditor5/ckeditor5.api.php, line 10


Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.