guideEditor bundle

# Overview

An editor bundle is the source code of a pre-configured and ready-to-use CKEditor 5, which is minified and built as a single file. To use the document storage, import and export or connection optimization features you need to upload an exact copy of the editor file from your application to the CKEditor Cloud Services server.

CKEditor Cloud Services uses your editor bundle to convert operations in the collaboration session into data. Thanks to this, CKEditor Cloud Services can ensure data consistency between users and the stored data. Furthermore, if your editor bundle is using some custom plugins that can output some custom data, it will be then properly handled by CKEditor Cloud Services.

Whenever you change something in your editor bundle or its configuration, you should update it on the CKEditor Cloud Services server, too. If the users use different versions of the editor than the one uploaded to the server, it may lead to data loss or even editor crashes.

# Building the editor bundle

The editor bundle that can be used by CKEditor Cloud Services must fulfill certain requirements:

  • It needs to be built with webpack into a single file.
  • The plugins should be added in the builtinPlugins property to be included in the editor bundle. You cannot use the config.plugins option for adding the plugins to the editor.
  • The output.library property in the webpack configuration should be set toCKEditorCS value.
  • Plugins used in the editor bundle cannot execute external HTTP requests.
  • The editor instance needs to be a default export in the editor bundle.

The officially supported CKEditor 5 plugins already meet the requirement of not executing external requests in case of using them in the editor bundle by CKEditor Cloud Services. If your custom plugins send any external requests then you have two options:

  • If this plugin does not modify the editor’s model or the editor’s output then you can remove it in editor configuration during the editor bundle upload.
  • If this plugin modifies the editor’s model you can refactor it into 2 plugins. One of them will take care of the editing part and only the other will make the HTTP requests. Thanks to this you will be able to remove one of the plugins during the editor bundle upload without affecting the output data.

Refer to the Uploading the editor bundle section for more information on removing the plugins from the editor configuration.

# Example

Check an example of creating an editor bundle in Node.js or follow this guide for a step-by-step tutorial on how to properly prepare and build the editor bundle that will be accepted by CKEditor Cloud Services during the upload. This example is using Node.js and npm, hence it is required to have these tools installed.

  1. Go to the CKEditor 5 online builder and configure your editor. Include the needed plugins and download the zip file with your configured editor.

  2. Extract the zip file.

  3. Open the webpack.config.js file and change the value of the output.library property to CKEditorCS:

module.exports = {
    ...
    output: {
        // The name under which the editor will be exported.
        library: 'CKEditorCS',
        ...
    },
    ...
};
  1. Run the following commands inside the extracted folder:
npm install
npm run build

After a successful build, you will find the ckeditor.js file inside the build folder. You can use this editor bundle in your application. Remember to use the new library name (CKEditorCS). If you are using a sample from the online builder, you need to change the library name in the index.html file:

    // index.html
   createDialog().then( cloudServicesConfig => {
---   return ClassicEditor
+++   return CKEditorCS
         .create( document.querySelector( '.collaboration-demo__editable' ), {

# Editor bundle with watchdog, context or “super builds”

Cloud Services expects the Editor class as default export in the bundled file. If you are using an editor bundle with watchdog, context or you are building multiple editors as “super build”, you need to take extra steps to be able to upload these editors.

You can build multiple editor bundles from multiple source files for different purposes. A single webpack build can be then used to bundle all of them. Thanks to this approach, you will have an editor bundle that is compatible with the Cloud Services and you can still use the other editor bundle and take advantage of the “super builds”, watchdog or context.

# Example of creating an editor bundle with watchdog

Assuming that you have ckeditor.js source file that is exporting the editor with watchdog:

import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor.js';
import EditorWatchdog from '@ckeditor/ckeditor5-watchdog/src/editorwatchdog';
// Other plugins imports.

class Editor extends ClassicEditor {}

Editor.builtinPlugins = [
    // Imported plugins.
];

const watchdog = new EditorWatchdog( Editor );
export default watchdog;

You can now create the ckeditor-cs.js source file with the same content as the above file. The only difference is the export in this file. Instead of exporting the watchdog, you should export the Editor instance: export default Editor;.

With the two source files, you can tweak the webpack config to bundle both editors in a single build step:

'use strict';

const path = require( 'path' );
const webpack = require( 'webpack' );
const { bundler, styles } = require( '@ckeditor/ckeditor5-dev-utils' );
const { CKEditorTranslationsPlugin } = require( '@ckeditor/ckeditor5-dev-translations' );
const TerserWebpackPlugin = require( 'terser-webpack-plugin' );

const config = {
    devtool: 'source-map',
    performance: { hints: false },
    optimization: {
        minimizer: [
            new TerserWebpackPlugin( {
                sourceMap: true,
                terserOptions: {
                    output: {
                        comments: /^!/
                    }
                },
                extractComments: false
            } )
        ]
    },
    plugins: [
        new CKEditorTranslationsPlugin( {
            language: 'en',
            additionalLanguages: 'all'
        } ),
        new webpack.BannerPlugin( {
            banner: bundler.getLicenseBanner(),
            raw: true
        } )
    ],
    module: {
        rules: [
            {
                test: /\.svg$/,
                use: [ 'raw-loader' ]
            },
            {
                test: /\.css$/,
                use: [
                    {
                        loader: 'style-loader',
                        options: {
                            injectType: 'singletonStyleTag',
                            attributes: {
                                'data-cke': true
                            }
                        }
                    },
                    {
                        loader: 'css-loader'
                    },
                    {
                        loader: 'postcss-loader',
                        options: {
                            postcssOptions: styles.getPostCssConfig( {
                                themeImporter: {
                                    themePath: require.resolve( '@ckeditor/ckeditor5-theme-lark' )
                                },
                                minify: true
                            } )
                        }
                    }
                ]
            }
        ]
    }
};

module.exports = [
    // The first bundle will have the editor with watchdog and can be used in your application.
    {
        ...config,
        entry: path.resolve( __dirname, 'src', 'ckeditor.js' ),
        output: {
            library: 'Watchdog',
            path: path.resolve( __dirname, 'build' ),
            filename: 'ckeditor.js',
            libraryTarget: 'umd',
            libraryExport: 'default'
        }
    },
    // The second bundle will be ready to be uploaded to the Cloud Services server.
    {
        ...config,
        entry: path.resolve( __dirname, 'src', 'ckeditor-cs.js' ),
        output: {
            library: 'CKEditorCS',
            path: path.resolve( __dirname, 'build' ),
            filename: 'ckeditor-cs.js',
            libraryTarget: 'umd',
            libraryExport: 'default'
        }
    }
];

Similarly, you can build multiple bundles if you are using context or “super builds”. In the case of a bundle with context make sure that the bundle for Cloud Services includes all the plugins that the context editor has. For super builds you can simply create a copy of a source file and export a single editor instance that you would like to upload to Cloud Services.

# Building the editor bundle with TypeScript

The examples presented above are using JavaScript in the editor source files. Building the editor bundle with TypeScript is also possible. To start using TypeScript follow these steps:

  1. Integrate TypeScript with webpack. Refer to this guide for more details.
  2. Change the extension of the editor source file to .ts. Adjust the entry path in webpack.config.js.
  3. Refer to the Working with TypeScript guide and adjust the imports and configuration settings if needed.

# Uploading the editor bundle

If you have already built your editor that meets the requirements, you can upload it to CKEditor Cloud Services together with the editor configuration. The editor upload process requires the use of the POST /editors method from the Cloud Services REST API.

Every request made to the CKEditor Cloud Services REST API needs to have additional headers: X-CS-Timestamp and X-CS-Signature. Refer to the request signature guide for more details.

Three parameters are passed to the body of this method:

  • bundle – The code of your minified editor passed as a string.
  • config – This is the editor configuration that will be used by the editor in CKEditor Cloud Services. This should be the same config as used by the editor in your application, especially when it can alter the data output.
  • test_data – This is the real document content generated by the editor you upload to the CKEditor Cloud Services server. It helps to detect if any of your custom plugins modify the document content incorrectly or if an invalid configuration has been passed. This parameter is optional but it is highly recommended to use it. You should make sure that your test data has been created using all the custom plugins you have implemented.

The body of the upload request should look like this:

{
  "bundle": "the content of editor bundle file",
  "config": {
    "cloudServices": {
      "bundleVersion": "unique_editor_bundle_version"
    },
    "removePlugins": [ "myCustomAutoSavePlugin" ],
    ... other config options
  },
  "test_data": "<p>example output from your editor</p>"
}

The bundleVersion property inside the config.cloudServices object is required.

Remember that the uploaded editor bundle is only available within the environment to which it was assigned. If you use the same editor for different environments, you must upload it separately for each of them.

# Editor configuration

The bundleVersion property set during an upload acts as a unique build identifier. It tells CKEditor Cloud Services which editor should be used when a user starts the collaboration session. You need to make sure that the editor configuration in your application also has the bundleVersion property and its value matches the editor uploaded to the CKEditor Cloud Services server. An example editor configuration with set bundleVersion looks like this:

return CKEditorCS
    .create( document.querySelector( '.collaboration-demo__editable' ), {
        cloudServices: {
            tokenUrl: 'TOKEN_URL',
            uploadUrl: 'UPLOAD_URL',
            webSocketUrl: 'WEBSOCKET_URL',
            bundleVersion: 'editor-1.0.0'
        },
        collaboration: {
            channelId: 'CHANNEL_ID'
        },
        toolbar: [
            // Toolbar items
        ],
        // Other config options
    } );

The editor configuration uploaded to the CKEditor Cloud Services server should be in JSON format. Therefore, in some cases, the config object needs to be modified before sending:

  • properties of particular plugins that require DOM elements should be set to null,
  • regex values in General HTML Support configuration should be converted to strings, e.g. "/.*/".

# Multi-root editor builds

For multi-root editor builds, an upload request body requires an additional is_multi_root_editor field set to true. The field indicates that the uploaded editor is a multi-root build. Additionally, the test_data property is expected to be a stringified JSON structure where the keys are the root names and the values are the HTML content for particular roots. Moreover, if you want to use the roots’ attributes you must provide example values for each root in the editor config.

{
  "bundle": "the content of a multi-root editor bundle file",
  "config": {
    "cloudServices": {
      "bundleVersion": "unique_multiroot_editor_bundle_version"
    },
    "removePlugins": [
      "myCustomAutoSavePlugin"
    ],
    "rootsAttributes": {
      "main": {
        "order": 1
      }
    },
    ... other config options
  },
  "test_data": "{\"header\":\"<p>Multi-root document header.</p>\",\"footer\":\"<p>Multi-root document footer.</p>\"}",
  "is_multi_root_editor": true
}

# Example

Follow this example to learn how to properly upload the editor bundle to CKEditor Cloud Services. This example is using Node.js and npm, hence it is required to have these tools installed. It also assumes that you have already built the editor bundle. This example will continue using the files from the previous example.

  1. In your terminal, use cd to get into the extracted folder and install the axios package with the npm i axios command.

  2. Create a new upload.js file inside the extracted folder with the following content:

const crypto = require( 'crypto' );
const fs = require( 'fs' );
const path = require( 'path' );
const axios = require( 'axios' );

// Update with your credentials and application endpoint
const environmentId = 'txQ9sTfqmXUyWU5LmDbr';
const apiSecret = '4zZBCQoPfRZ7Rr7TEnGAuRsGgbfF58Eg0PA8xcLD2kvPhjGjy4VGgB8k0hXn';
const applicationEndpoint = 'https://33333.cke-cs.com';

const apiEndpoint = `${ applicationEndpoint }/api/v5/${ environmentId }/editors/`;
const editorBundle = fs.readFileSync( path.resolve( './build/ckeditor.js' ) );

const body = {
   bundle: editorBundle.toString(),
   config: {
      cloudServices: {
         bundleVersion: 'editor-1.0.0' // Set a unique name for the uploaded bundle
      },
        toolbar: [
            // Toolbar items
        ],
        // Other config options
   }
};

const CSTimestamp = Date.now();
const axiosConfig = {
   headers: {
      'X-CS-Timestamp': CSTimestamp,
      'X-CS-Signature': generateSignature( apiSecret, 'POST', apiEndpoint, CSTimestamp, body )
   }
};

axios.post( apiEndpoint, body, axiosConfig )
   .then( response => {
      console.log ( response.status );
   } ).catch( error => {
      console.log( error.message );
      console.log( error.response.data );
   } );

function generateSignature( apiSecret, method, uri, timestamp, body ) {
   const url = new URL( uri );
   const path = url.pathname + url.search;
   const hmac = crypto.createHmac( 'SHA256', apiSecret );

   hmac.update( `${ method.toUpperCase() }${ path }${ timestamp }` );

   if ( body ) {
      hmac.update( Buffer.from( JSON.stringify( body ) ) );
   }

   return hmac.digest( 'hex' );
}

Run the above code with the node upload.js command.

You should see the 201 status code in your console. You can run node upload.js once again to make sure that the bundle is already uploaded. In this case, you will get a 409 error with message Editor editor-1.0.0 already exists.

# Updating the editor bundle

As shown in the example above, it is not possible to overwrite an editor bundle on the CKEditor Cloud Services server. You may, however, encounter situations where you need to rebuild your editor, for example when you add a new plugin, update CKEditor 5 version or fix a bug in your custom plugin. You should follow these steps to start using a new editor bundle:

  1. Rebuild your editor with the necessary changes to the configuration or plugins.
  2. Upload the new editor bundle with the bundleVersion value being new and unique for your environment.
  3. Update the bundleVersion in the editor configuration in your application to match the new value from step 2.
  4. Wait for the active collaboration sessions to be removed automatically or remove them manually using the DELETE /collaborations/{document_id} from the CKEditor Cloud Services REST API.
  5. Open the document in your new editor.

A collaboration session initiated with certain bundleVersion can be opened only by editors with the same bundleVersion value. Because of this you can not connect to the previously edited documents with the new editor, without reinitializing such collaboration session (steps 4 and 5).

To ensure data consistency you should update the editor bundle on CKEditor Cloud Services every time there is a change in your editor. This is especially important when the changes are related to the conversions or the data outputs.