Report an issue

guideReal-time collaborative features integration

Complementary to this guide, we provide ready-to-use samples available for download. We prepared samples for all editor types (multi-root included) as well as for the React, Angular and Vue.js integrations. You may use them as an example or as a starting point for your own integration.

The real-time collaboration feature needs a cloud service to synchronize content between the clients, so first you need to sign up to the Collaboration service powered by CKEditor Cloud Services. Refer to CKEditor Cloud Services Collaboration - Quick Start for more details.

After that, you need to create a custom CKEditor 5 build that will include the real-time collaboration feature, because it is not included in any of the official builds. The following instructions describe step-by-step what you need to do.

Real-time collaboration is a complex topic and despite having over 10.000 tests we cannot guarantee that no error will show during a long collaboration session. This is why we recommend to use the watchdog, which is a helpful utility that ensures that an editor instance is running and in case of an error, tries to restore the editor to the working state. While using collaboration features without the watchdog is possible, we highly recommend to add it.

First, clone the editor build that you want to integrate. This example uses the classic build, however, real-time collaboration, like every other official CKEditor 5 plugin, will work with any rich-text editor build.

git clone -b stable https://github.com/ckeditor/ckeditor5-build-classic.git ckeditor5-with-real-time-collaboration
cd ckeditor5-with-real-time-collaboration
npm install

To add real-time collaborative features to your editor, install the @ckeditor/ckeditor5-real-time-collaboration and @ckeditor/ckeditor5-watchdog packages:

npm install --save-dev \
    @ckeditor/ckeditor5-real-time-collaboration \
    @ckeditor/ckeditor5-watchdog

Then import and enable the plugins you want to use and export both the editor and the watchdog. The updated src/ckeditor.js should look like this:

import Watchdog from '@ckeditor/ckeditor5-watchdog/src/watchdog';
import ClassicEditorBase from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
import ImageToolbar from '@ckeditor/ckeditor5-image/src/imagetoolbar';
import EasyImageUpload from '@ckeditor/ckeditor5-easy-image/src/easyimage';

import RealTimeCollaborativeEditing from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativeediting';

// The following plugin enables real-time collaborative comments.
// You do not need to import it if you do not want to integrate it.
import RealTimeCollaborativeComments from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativecomments';

// The following plugin enables real-time collaborative track changes and is optional.
// You do not need to import it if you do not want to integrate it.
import RealTimeCollaborativeTrackChanges from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativetrackchanges';

// The following plugin enables users presence list and is optional.
// You do not need to import it if you do not want to integrate it.
import PresenceList from '@ckeditor/ckeditor5-real-time-collaboration/src/presencelist';

class ClassicEditor extends ClassicEditorBase {}

// Plugins to include in the build.
ClassicEditor.builtinPlugins = [
    Essentials, Paragraph, Bold, Italic, ImageToolbar, EasyImageUpload,
    // Remove plugins from here if you have not imported them.
    RealTimeCollaborativeEditing, RealTimeCollaborativeComments, RealTimeCollaborativeTrackChanges, PresenceList
];

// Editor configuration.
ClassicEditor.defaultConfig = {
    language: 'en'
};

export default { ClassicEditor, Watchdog };

After that, your custom build needs to be bundled using webpack:

npm run build

Read more about installing plugins.

Then, you need to place your bundle in an HTML document and create the editor.

As for configuration, you need to define the CKEditor Cloud Services connection data and add buttons to toolbars.

If you do not have your CKEditor Cloud Services URLs yet, read more about them in the CKEditor Cloud Services Collaboration - Quick Start guide.

For completeness sake, the example below implements wide sidebar display mode for comments and suggestions annotations. If you want to use inline display mode, remove parts of the snippets that set up the sidebar.

The updated HTML file (sample/index.html) should look like this:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>CKEditor 5 Collaboration – Hello World!</title>

        <style type="text/css">
         #container {
             /* To create the column layout. */
             display: flex;

             /* To make the container relative to its children. */
             position: relative;
         }

         #container .ck.ck-editor {
             /* To stretch the editor to max 700px
                (just to look nice for this example but it can be any size). */
             width: 100%;
             max-width: 700px;
         }

         #sidebar {
            /* Set some size for the sidebar (it can be any). */
            min-width: 300px;

            /* Add some distance. */
            padding: 0 10px;
         }
        </style>
    </head>

    <div id="presence-list-container"></div>

    <div id="container">
        <div id="editor"></div>
        <div id="sidebar"></div>
    </div>

    <script src="../build/ckeditor.js"></script>
    <script>
        // We are working on distributing watchdog as a separate package.
        // See https://github.com/ckeditor/ckeditor5-watchdog/issues/9.
        const { ClassicEditor: Editor, Watchdog } = ClassicEditor;

        const watchdog = Watchdog.for( Editor );

        watchdog.create( document.querySelector( '#editor' ), {
            initialData: '<p>Let\'s edit this together!</p>',
            // Do not include the `'comment'` or `'trackChanges'` buttons if you do not integrate them.
            // If you use the image toolbar, you might want to add the `'comment'` button to the image toolbar, too.
            toolbar: [ 'bold', 'italic', 'imageUpload', '|', 'comment', 'trackChanges' ],
            cloudServices: {
                // PROVIDE CORRECT VALUES HERE:
                tokenUrl: 'https://example.com/cs-token-endpoint',
                uploadUrl: 'https://your-organization-id.cke-cs.com/easyimage/upload/',
                webSocketUrl: 'your-organization-id.cke-cs.com/ws/',
                documentId: 'collaboration'
            },
            sidebar: {
                container: document.querySelector( '#sidebar' )
            },
            presenceList: {
                container: document.querySelector( '#presence-list-container' )
            }
        } );
    </script>
</html>

Note that to make the integration work out of the box, all comments and suggestions data is saved in CKEditor Cloud Services, however, some markup is added to the content, too. To learn more, refer to the Comments markup and Suggestions markup sections of the documentation.

Do note that CKEditor Cloud Services does not save or load the document content. It is a medium to handle the collaboration process. Content management should be handled by the integrated application.

We understand that due to legislation or your company’s policy you may be required to store all data in your own data center or private cloud. This is why we also provide the on-premises version of CKEditor Cloud Services. Contact us to learn more about it.

Voilà! All users who open this page should be able to collaborate, working on the same rich-text document at the same time.

# The documentId property

The documentId is an important property that controls which editor instances collaborate with one another. All editor instances created with the same documentId will be connected.

Each document must have a different document ID. The ID is usually the primary key of the document in the database, but you are free to provide whatever identifier fits your scenario. The documentId needs to be unique in the environment so if you are using, for instance, staging and production environments, you may use the same document ID. Since the environments are separated, they will not collaborate with one another.

# Data initialization

When the first user with a certain document ID opens the rich-text editor, their content is sent to CKEditor Cloud Services. In case of the example above it is:

<p>Let's edit this together!</p>

Then, when the next user connects to the same document, they receive content from CKEditor Cloud Services and their local content is discarded.

This is the reason why you should not use the editor.setData() or editor.data.set() methods when using the collaboration plugin. The content of the editor needs to be defined before the editor initialization, so the collaboration plugin can decide which data should be used. Use the initialData configuration option instead.

Keep in mind that the editor.setData() and editor.data.set() methods simply overwrite the content of the editor. The entire existing content is removed and the new content is loaded, even if it is the same. In collaboration, this behavior can cause issues. The local data will be overwritten on all clients, which means that some data might be lost. In most cases, using editor.setData() and editor.data.set() in real-time collaboration is a mistake either in the integration or a third-party plugin. For that reason, the editor throws an error when this is attempted.

If you are sure that you understand and accept the behavior and effects of setting the data in this way, use editor.data.set() with the flag suppressErrorInCollaboration set to true, for example:

editor.data.set( '<p>Your data</p>', { suppressErrorInCollaboration: true } );

This will let you overwrite the editor data without throwing an error.

Note that to start the collaboration on a new document, you need the document ID from the very beginning, when the editor is created for the first time. If your application creates document IDs later, for instance when the form is submitted, you may need another method to generate document IDs (for example, some random unique IDs generator).

The document is removed from CKEditor Cloud Services a while after the last user disconnects.

# Saving data

The document is temporarily stored in the cloud by CKEditor Cloud Services only when there are connected users. It means that the content should be saved in the database on your server at the very latest when the last user disconnects — otherwise it may get lost.

Note that CKEditor 5 will handle the real-time synchronization of the content in the editor between collaborating clients. You need to take care of the collaborative workflow in the rest of your application. For instance, if the WYSIWYG editor is a part of a bigger data form, you need to make the entire form collaborative. The classic data form with the “Save” button might not work for you since the data will not be saved in real time in your database even if the editor will synchronize the content between all your clients. It is recommended to save the data automatically whenever it changes.

CKEditor 5 provides two utilities to make your integration simpler.

# The cloudDocumentVersion property

The first helper is the cloudDocumentVersion property. When your collaborative users are saving the same document at the same time, there might be a conflict. It might happen that an older document version overwrites the new one.

To prevent race conditions, the cloudDocumentVersion variable is provided:

editor.plugins.get( 'RealTimeCollaborationClient' ).cloudDocumentVersion

This property is simply a number and a bigger value means a newer document version. This number should be stored in the database together with the content. When any client wants to save the content, document versions should be compared. The document should only be saved when the version is higher. Otherwise, it means that this is an older version of the document and there is already a newer version saved.

The cloudDocumentVersion is stored in the CKEditor Cloud Services server between collaboration sessions so if you start a new session, this property will equal the number it reached at the end of the previous session.

# The Autosave plugin

The second helper is the Autosave plugin.

The autosave plugin triggers the save callback whenever the user changes the content. It takes care of throttling the callback execution so it is not called too often. It also automatically secures the user from leaving the page before the content is saved.

The autosave plugin is not included in any of the builds by default nor is it automatically required by the real-time collaboration plugins so you need to install it separately. To learn more about this plugin refer to the Getting and saving data guide.

# Users selection

The real-time collaborative editing feature not only synchronizes the document content between all participants, but it also shows each user the selection of all other users in real time. It works out of the box and does not require any additional configuration. This is the only part of the real-time collaborative editing plugin that provides a UI. See the Users in real-time collaboration guide to learn more.