Report an issue

guideIntegrating revision history with your application

The revision history plugin provides an API that lets you create and manage named revisions of your document. To save and access revisions in your database, you first need to integrate this feature.

This guide describes integrating the revision history feature as a standalone plugin.

If you are using the real-time collaboration feature, refer to the Real-time collaboration features integration guide.

# Before you start

This guide will discuss two ways to integrate CKEditor 5 with the revision history plugin:

The adapter integration is the recommended approach for two reasons:

  • It gives you better control over the data.
  • It is more efficient and provides a better user experience, as the revisions’ data is loaded on demand rather than upfront before the editor is initialized.

Before you start creating an integration, there are a few concepts you should be familiar with. This guide will explain how to create a custom build with the revision history plugin, what data structure the revisions use, and what the plugin API looks like.

Make sure that you understand these concepts before you proceed with the integration.

# Activating the feature

To use this premium feature, you need to activate it with proper credentials. Refer to the License key and activation guide for details.

After obtaining all the credentials needed, create a custom editor and configure it.

# Prepare a custom build

The revision history plugin is not included by default in any CKEditor 5 build. To enable it, you need to create a custom CKEditor 5 build that includes the revision history plugin.

You can also create your own CKEditor 5 build with the revision history feature included using the CKEditor5 online builder.

git clone -b stable https://github.com/ckeditor/ckeditor5
cd ckeditor5/packages/ckeditor5-build-classic

npm install

Install the @ckeditor5/ckeditor5-revision-history package using npm:

npm install --save-dev @ckeditor/ckeditor5-revision-history

To make the revision history work, import the revision history plugin and add it to the list of plugins.

An updated src/ckeditor.js should look like this:

import { ClassicEditor as ClassicEditorBase } from '@ckeditor/ckeditor5-editor-classic';

import { Autoformat } from '@ckeditor/ckeditor5-autoformat';
import { Bold, Italic } from '@ckeditor/ckeditor5-basic-styles';
import { Essentials } from '@ckeditor/ckeditor5-essentials';
import { Image } from '@ckeditor/ckeditor5-image';
import { Paragraph } from '@ckeditor/ckeditor5-paragraph';
import { Table } from '@ckeditor/ckeditor5-table';

import { RevisionHistory } from '@ckeditor/ckeditor5-revision-history';

export default class ClassicEditor extends ClassicEditorBase {}

// Plugins to include in the build.
ClassicEditor.builtinPlugins = [ Essentials, Paragraph, Bold, Italic, Image, Table, RevisionHistory ];

// The editor configuration.
ClassicEditor.defaultConfig = {
    language: 'en',

    // Provide the configuration for the comments feature.
    comments: {
        editorConfig: {
            // The list of plugins that will be included in the comments editors.
            extraPlugins: [ Bold, Italic, Autoformat ]
        }
    }
};

You need to bundle your custom build using webpack:

npm run build

Read more about this topic in the installing plugins guide.

# Core setup

The examples below implement the wide sidebar display mode to show comments and suggestions.

When you have the revision history package included in your custom build, prepare an HTML structure for the editor sidebar and revision history. After that, you can enable the revision history plugin.

To browse through the saved revisions, the feature uses an additional editor instance on top of the original editor instance. To enable that, you will need to provide an HTML structure for both the original editor and the revision history editor. The HTML elements for the revision history editor structure should be hidden by default.

To set up the feature, you need to:

  • Add the HTML layout for the document editor and the revision history editor.
  • Add the sidebar and revisionHistory configurations.
  • Add the revisionHistory dropdown to the toolbar.
  • Add your licenseKey. If you do not have a key yet, contact us.

Edit the sample/index.html file as follows:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>CKEditor&nbsp;5 with revision history</title>
    <style type="text/css">
    .editor-container {
        display: flex;
        flex-direction: row;
        flex-wrap: nowrap;
        position: relative;
        width: 1260px;
    }

    .ck.ck-editor {
        max-width: 800px;
    }

    #revision-viewer-container {
        display: none;
    }

    .editor-container > .ck-editor {
        position: relative;
        width: 950px;
    }

    .editor-container .ck-editor__top .ck-toolbar {
        border-top-right-radius: 0;
        border-bottom-right-radius: 0;
    }

    .editor-container .ck-editor__editable_inline {
        border-top-right-radius: 0;
        border-bottom-right-radius: 0;
    }

    .sidebar-container {
        position: relative;
        width: 600px;
        overflow: hidden;
        background: var(--ck-color-toolbar-background);
        border: 1px solid var(--ck-color-toolbar-border);
        margin-left: -1px;
    }
    </style>
</head>
<body>

<div class="editors-holder">
    <!-- The original editor. !-->
    <div id="editor-container">
        <div class="editor-container">
            <div id="editor"></div>
            <div class="sidebar-container" id="sidebar-container"></div>
        </div>
    </div>

    <!-- Structure for the revision viewer editor. !-->
    <div id="revision-viewer-container">
        <div class="editor-container">
            <div id="revision-viewer-editor"></div>
            <div class="sidebar-container" id="revision-viewer-sidebar"></div>
        </div>
    </div>
</div>
<script src="../build/ckeditor.js"></script>
<script>
    ClassicEditor.create( document.querySelector( '#editor' ), {
        licenseKey: 'your-license-key',
        sidebar: {
            container: document.querySelector( '#sidebar-container' )
        },
        revisionHistory: {
            editorContainer: document.querySelector( '#editor-container' ),
            viewerContainer: document.querySelector( '#revision-viewer-container' ),
            viewerEditorElement: document.querySelector( '#revision-viewer-editor' ),
            viewerSidebarContainer: document.querySelector( '#revision-viewer-sidebar' )
        },
        toolbar: {
            items: [
                'undo', 'redo',
                '|', 'revisionHistory', '|', 'trackChanges', 'comment',
                '|', 'heading',
                '|', 'fontsize', 'fontfamily', 'fontColor', 'fontBackgroundColor',
                '-', 'bold', 'italic', 'underline',
                '|', 'link', 'uploadImage', 'insertTable', 'blockQuote', 'codeBlock',
                '|', 'alignment',
                '|', 'bulletedList', 'numberedList'
            ],
            shouldNotGroupWhenFull: true
        }
    } )
    .catch( error => console.error( error ) );
</script>

</body>
</html>

When you open the sample in the browser, you should see the WYSIWYG editor with the revision history plugin. It still does not load or save any revision data and the user is not defined. You will learn how to add those later in this guide.

# A simple “load and save” integration

In this solution, you load the user and revision data during the editor initialization. You save the revision data after you finish working with the editor (for example, when you submit the form containing the WYSIWYG editor).

The integration below uses the revision history API. Making yourself familiar with the API may help you understand the code snippets. In case of any problems, refer to the revision history API documentation.

# Loading the data

After you include the revision history plugin in the editor, you need to create a plugin that will initialize users and existing revisions.

You should load the revisions data during the plugin initialization step, that is, using the init() method of the integration plugin that you will provide (as in the example below).

First, dump the users and the revisions data into a variable that will be available for your plugin.

The revisions data may be heavy for large documents. This will have an impact on your application loading time. Since the adapter integration loads the revisions data on demand, it is the recommended integration type for applications handling bigger documents with many revisions.

If your application needs to request the revisions data from the server asynchronously, you can create a plugin that will fetch the data from the database instead of putting the data in the HTML source. In such a case, your plugin should return a Promise from the Plugin.init method to make sure that the editor initialization waits for your data.

You can also refer to an example shown in the adapter integration section.

// Application data will be available under a global variable `appData`.
const appData = {
    // The initial editor data.
    data: `<figure class="image">
        <img src="https://ckeditor.com/docs/ckeditor5/latest/assets/img/revision-history-demo.png">
    </figure>
    <h1>PUBLISHING AGREEMENT</h1>
    <h3>Introduction</h3>
    <p>This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”.</p>
    <h3>Grant of Rights</h3>
    <p>The Author grants the Publisher full right and title to the following, in perpetuity:</p>
    <ul>
        <li>To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future.</li>
        <li>To create or devise modified, abridged, or derivative works based on the works listed.</li>
        <li>To allow others to use the listed works at their discretion, without providing additional compensation to the Author.</li>
    </ul>
    <p>These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future.</p>
    <p>Any rights not granted to the Publisher above remain with the Author.</p>
    <p>The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature.</p>`,

    // The user data.
    user: {
        id: 'u1',
        name: 'Mex Haddox'
    },

    // The initial revisions data.
    revisions: [
        {
            "id": "initial",
            "name": "Initial revision",
            "creatorId": "u1",
            "authorsIds": [ "u1" ],
            "diffData": {
                "main": {
                    "insertions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ………… by and between The Lower Shelf, the “Publisher”, and …………, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him/herself and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]},{"name":"p","attributes":[],"children":["Publishing formats are enumerated in Appendix A."]}]',
                    "deletions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ………… by and between The Lower Shelf, the “Publisher”, and …………, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him/herself and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]},{"name":"p","attributes":[],"children":["Publishing formats are enumerated in Appendix A."]}]'
                }
            },
            "createdAt": "2021-05-27T13:22:59.077Z",
            "attributes": {},
            "fromVersion": 1,
            "toVersion": 1
        },
        {
            "id": "e6f80e6be6ee6057fd5a449ab13fba25d",
            "name": "Updated with the actual data",
            "creatorId": "u1",
            "authorsIds": [ "u1" ],
            "diffData": {
                "main": {
                    "insertions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ",{"name":"revision-start","attributes":[["name","insertion:u1:0"]],"children":[]},"1st",{"name":"revision-end","attributes":[["name","insertion:u1:0"]],"children":[]}," ",{"name":"revision-start","attributes":[["name","insertion:u1:1"]],"children":[]},"June 2020 ",{"name":"revision-end","attributes":[["name","insertion:u1:1"]],"children":[]},"by and between The Lower Shelf, the “Publisher”, and ",{"name":"revision-start","attributes":[["name","insertion:u1:2"]],"children":[]},"John Smith",{"name":"revision-end","attributes":[["name","insertion:u1:2"]],"children":[]},", the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]',
                    "deletions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ",{"name":"revision-start","attributes":[["name","deletion:u1:0"]],"children":[]},"…………",{"name":"revision-end","attributes":[["name","deletion:u1:0"]],"children":[]}," by and between The Lower Shelf, the “Publisher”, and ",{"name":"revision-start","attributes":[["name","deletion:u1:1"]],"children":[]},"…………",{"name":"revision-end","attributes":[["name","deletion:u1:1"]],"children":[]},", the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him",{"name":"revision-start","attributes":[["name","deletion:u1:2"]],"children":[]},"/herself",{"name":"revision-end","attributes":[["name","deletion:u1:2"]],"children":[]}," and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature.",{"name":"revision-start","attributes":[["name","deletion:u1:3"]],"children":[]}]},{"name":"p","attributes":[],"children":["Publishing formats are enumerated in Appendix A.",{"name":"revision-end","attributes":[["name","deletion:u1:3"]],"children":[]}]}]'
                }
            },
            "createdAt": "2021-05-27T13:23:52.553Z",
            "attributes": {},
            "fromVersion": 1,
            "toVersion": 20
        },
        {
            "id": "e6590c50ccbc86acacb7d27231ad32064",
            "name": "Inserted logo",
            "creatorId": "u1",
            "authorsIds": [ "u1" ],
            "diffData": {
                "main": {
                    "insertions": '[{"name":"figure","attributes":[["data-revision-start-before","insertion:u1:0"],["class","image"]],"children":[{"name":"img","attributes":[["src","https://ckeditor.com/docs/ckeditor5/latest/assets/img/revision-history-demo.png"]],"children":[]}]},{"name":"h1","attributes":[],"children":[{"name":"revision-end","attributes":[["name","insertion:u1:0"]],"children":[]},"PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]',
                    "deletions": '[{"name":"h1","attributes":[["data-revision-start-before","deletion:u1:0"]],"children":[{"name":"revision-end","attributes":[["name","deletion:u1:0"]],"children":[]},"PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]'
                }
            },
            "createdAt": "2021-05-27T13:26:39.252Z",
            "attributes": {},
            "fromVersion": 20,
            "toVersion": 24
        },
        // An empty current revision.
        {
            "id": "egh91t5jccbi894cacxx7dz7t36aj3k021",
            "name": null,
            "creatorId": null,
            "authorsIds": [],
            "diffData": {
                "main": {
                    "insertions": '[{"name":"figure","attributes":[["class","image"]],"children":[{"name":"img","attributes":[["src","https://ckeditor.com/docs/ckeditor5/latest/assets/img/revision-history-demo.png"]],"children":[]}]},{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]',
                    "deletions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]'
                }
            },
            "createdAt": "2021-05-27T13:26:39.252Z",
            "attributes": {},
            "fromVersion": 24,
            "toVersion": 24
        }
    ]
};

Then, prepare a plugin that will read the data from appData and use the Users and RevisionsRepository API.

class RevisionHistoryIntegration {
    constructor( editor ) {
        this.editor = editor;
    }

    static get pluginName() {
        return 'RevisionHistoryIntegration';
    }

    static get requires() {
        return [ 'RevisionHistory' ];
    }

    init() {
        const revisionHistory = this.editor.plugins.get( 'RevisionHistory' );

        for ( const revisionData of appData.revisions ) {
            revisionHistory.addRevisionData( revisionData );
        }
    }
}

// Initialize users here.
class UsersInit {
    constructor( editor ) {
        this.editor = editor;
    }

    static get pluginName() {
        return 'UsersInit';
    }

    static get requires() {
        return [ 'Users' ];
    }

    init() {
        const users = this.editor.plugins.get( 'Users' );

        users.addUser( appData.user );
        users.defineMe( appData.user.id );
    }
}

// To load comments and track changes,
// create the comments and track changes integrations.

# Saving the data

You must keep the document data and revisions data in sync for the feature to work correctly.

You must always save revisions data and document data together.

To save the revisions data, you need to get it from the RevisionsRepository plugin first. To do this, use the getRevisions() method.

Then, use the data to save it in your database in a selected way. See the example below. Remember to update your HTML structure to contain a button with the get-data ID, for example, <button id="get-data">Get editor data</button>.

ClassicEditor
    .create( document.querySelector( '#editor' ), {
        initialData: appData.data,
        extraPlugins: [ UsersInit, RevisionHistoryIntegration ],
        licenseKey: 'your-license-key',
        sidebar: {
            container: document.querySelector( '#sidebar-container' )
        },
        revisionHistory: {
            editorContainer: document.querySelector( '#editor-container' ),
            viewerContainer: document.querySelector( '#revision-viewer-container' ),
            viewerEditorElement: document.querySelector( '#revision-viewer-editor' ),
            viewerSidebarContainer: document.querySelector( '#revision-viewer-sidebar' )
        },
        toolbar: {
            items: [ 'bold', 'italic', '|', 'revisionHistory' ]
        }
    } )
    .then( editor => {
        // After the editor is initialized, add an action to be performed after a button is clicked.
        document.querySelector( '#get-data' ).addEventListener( 'click', () => {
            const revisionHistory = editor.plugins.get( 'RevisionHistory' );

            // Get the document data and the revisions data (in JSON format, so it is easier to save).
            const editorData = editor.data.get();
            const revisionsData = revisionHistory.getRevisions( { toJSON: true } );

            // Now, use `editorData` and `revisionsData` to save the data in your application.
            //
            // Note: it is a good idea to verify the revision `creatorId` parameter when saving
            // a revision in the database. However, do not overwrite the value if it was set to `null`!
            //
            console.log( editorData );
            console.log( revisionsData );
        } );
    } )
    .catch( error => console.error( error ) );

It is recommended to stringify the revisions’ attributes property value to JSON and save it as a string in your database. Then parse the value from JSON when loading revisions.

# Full implementation

Below you can find the final implementation.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>CKEditor&nbsp;5 with revision history</title>
    <style type="text/css">
    .editor-container {
        display: flex;
        flex-direction: row;
        flex-wrap: nowrap;
        position: relative;
        width: 1260px;
    }

    #revision-viewer-container {
        display: none;
    }

    .editor-container > .ck-editor {
        position: relative;
        width: 950px;
    }

    .editor-container .ck-editor__top .ck-toolbar {
        border-top-right-radius: 0;
        border-bottom-right-radius: 0;
    }

    .editor-container .ck-editor__editable_inline {
        border-top-right-radius: 0;
        border-bottom-right-radius: 0;
    }

    .sidebar-container {
        position: relative;
        width: 600px;
        overflow: hidden;
        background: var(--ck-color-toolbar-background);
        border: 1px solid var(--ck-color-toolbar-border);
        margin-left: -1px;
    }
    </style>
</head>
<body>
<button id="get-data">Get editor data</button>
<div class="editors-holder">
    <!-- The original editor. !-->
    <div id="editor-container">
        <div class="editor-container">
            <div id="editor"></div>
            <div class="sidebar-container" id="sidebar-container"></div>
        </div>
    </div>

    <!-- Structure for the revision viewer editor. !-->
    <div id="revision-viewer-container">
        <div class="editor-container">
            <div id="revision-viewer-editor"></div>
            <div class="sidebar-container" id="revision-viewer-sidebar"></div>
        </div>
    </div>
</div>
<script src="../build/ckeditor.js"></script>
<script>
    // Application data will be available under a global variable `appData`.
    const appData = {
        // The initial editor data.
        data: `<figure class="image">
        <img src="https://ckeditor.com/docs/ckeditor5/latest/assets/img/revision-history-demo.png">
    </figure>
    <h1>PUBLISHING AGREEMENT</h1>
    <h3>Introduction</h3>
    <p>This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”.</p>
    <h3>Grant of Rights</h3>
    <p>The Author grants the Publisher full right and title to the following, in perpetuity:</p>
    <ul>
        <li>To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future.</li>
        <li>To create or devise modified, abridged, or derivative works based on the works listed.</li>
        <li>To allow others to use the listed works at their discretion, without providing additional compensation to the Author.</li>
    </ul>
    <p>These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future.</p>
    <p>Any rights not granted to the Publisher above remain with the Author.</p>
    <p>The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature.</p>`,

        // The user data.
        user: {
            id: 'u1',
            name: 'Mex Haddox'
        },

        // The initial revisions data.
        revisions: [
            {
                "id": "initial",
                "name": "Initial revision",
                "creatorId": "u1",
                "authorsIds": [ "u1" ],
                "diffData": {
                    "main": {
                        "insertions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ………… by and between The Lower Shelf, the “Publisher”, and …………, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him/herself and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]},{"name":"p","attributes":[],"children":["Publishing formats are enumerated in Appendix A."]}]',
                        "deletions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ………… by and between The Lower Shelf, the “Publisher”, and …………, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him/herself and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]},{"name":"p","attributes":[],"children":["Publishing formats are enumerated in Appendix A."]}]'
                    }
                },
                "createdAt": "2021-05-27T13:22:59.077Z",
                "attributes": {},
                "fromVersion": 1,
                "toVersion": 1
            },
            {
                "id": "e6f80e6be6ee6057fd5a449ab13fba25d",
                "name": "Updated with the actual data",
                "creatorId": "u1",
                "authorsIds": [ "u1" ],
                "diffData": {
                    "main": {
                        "insertions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ",{"name":"revision-start","attributes":[["name","insertion:u1:0"]],"children":[]},"1st",{"name":"revision-end","attributes":[["name","insertion:u1:0"]],"children":[]}," ",{"name":"revision-start","attributes":[["name","insertion:u1:1"]],"children":[]},"June 2020 ",{"name":"revision-end","attributes":[["name","insertion:u1:1"]],"children":[]},"by and between The Lower Shelf, the “Publisher”, and ",{"name":"revision-start","attributes":[["name","insertion:u1:2"]],"children":[]},"John Smith",{"name":"revision-end","attributes":[["name","insertion:u1:2"]],"children":[]},", the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]',
                        "deletions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ",{"name":"revision-start","attributes":[["name","deletion:u1:0"]],"children":[]},"…………",{"name":"revision-end","attributes":[["name","deletion:u1:0"]],"children":[]}," by and between The Lower Shelf, the “Publisher”, and ",{"name":"revision-start","attributes":[["name","deletion:u1:1"]],"children":[]},"…………",{"name":"revision-end","attributes":[["name","deletion:u1:1"]],"children":[]},", the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him",{"name":"revision-start","attributes":[["name","deletion:u1:2"]],"children":[]},"/herself",{"name":"revision-end","attributes":[["name","deletion:u1:2"]],"children":[]}," and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature.",{"name":"revision-start","attributes":[["name","deletion:u1:3"]],"children":[]}]},{"name":"p","attributes":[],"children":["Publishing formats are enumerated in Appendix A.",{"name":"revision-end","attributes":[["name","deletion:u1:3"]],"children":[]}]}]'
                    }
                },
                "createdAt": "2021-05-27T13:23:52.553Z",
                "attributes": {},
                "fromVersion": 1,
                "toVersion": 20
            },
            {
                "id": "e6590c50ccbc86acacb7d27231ad32064",
                "name": "Inserted logo",
                "creatorId": "u1",
                "authorsIds": [ "u1" ],
                "diffData": {
                    "main": {
                        "insertions": '[{"name":"figure","attributes":[["data-revision-start-before","insertion:u1:0"],["class","image"]],"children":[{"name":"img","attributes":[["src","https://ckeditor.com/docs/ckeditor5/latest/assets/img/revision-history-demo.png"]],"children":[]}]},{"name":"h1","attributes":[],"children":[{"name":"revision-end","attributes":[["name","insertion:u1:0"]],"children":[]},"PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]',
                        "deletions": '[{"name":"h1","attributes":[["data-revision-start-before","deletion:u1:0"]],"children":[{"name":"revision-end","attributes":[["name","deletion:u1:0"]],"children":[]},"PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]'
                    }
                },
                "createdAt": "2021-05-27T13:26:39.252Z",
                "attributes": {},
                "fromVersion": 20,
                "toVersion": 24
            },
            // An empty current revision.
            {
                "id": "egh91t5jccbi894cacxx7dz7t36aj3k021",
                "name": null,
                "creatorId": null,
                "authorsIds": [],
                "diffData": {
                    "main": {
                        "insertions": '[{"name":"figure","attributes":[["class","image"]],"children":[{"name":"img","attributes":[["src","https://ckeditor.com/docs/ckeditor5/latest/assets/img/revision-history-demo.png"]],"children":[]}]},{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]',
                        "deletions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]'
                    }
                },
                "createdAt": "2021-05-27T13:26:39.252Z",
                "attributes": {},
                "fromVersion": 24,
                "toVersion": 24
            }
        ]
    };

    class RevisionHistoryIntegration {
        constructor( editor ) {
            this.editor = editor;
        }

        static get pluginName() {
            return 'RevisionHistoryIntegration';
        }

        static get requires() {
            return [ 'RevisionHistory' ];
        }

        init() {
            const revisionHistory = this.editor.plugins.get( 'RevisionHistory' );

            for ( const revisionData of appData.revisions ) {
                revisionHistory.addRevisionData( revisionData );
            }
        }
    }

    // Initialize users here.
    class UsersInit {
        constructor( editor ) {
            this.editor = editor;
        }

        static get pluginName() {
            return 'UsersInit';
        }

        static get requires() {
            return [ 'Users' ];
        }

        init() {
            const users = this.editor.plugins.get( 'Users' );

            users.addUser( appData.user );
            users.defineMe( appData.user.id );
        }
    }

    // To load comments and track changes,
    // create the comments and track changes integrations.

    ClassicEditor
        .create( document.querySelector( '#editor' ), {
            initialData: appData.data,
            extraPlugins: [ UsersInit, RevisionHistoryIntegration ],
            licenseKey: 'your-license-key',
            sidebar: {
                container: document.querySelector( '#sidebar-container' )
            },
            revisionHistory: {
                editorContainer: document.querySelector( '#editor-container' ),
                viewerContainer: document.querySelector( '#revision-viewer-container' ),
                viewerEditorElement: document.querySelector( '#revision-viewer-editor' ),
                viewerSidebarContainer: document.querySelector( '#revision-viewer-sidebar' )
            },
            toolbar: {
                items: [ 'bold', 'italic', '|', 'revisionHistory' ]
            }
        } )
        .then( editor => {
            // After the editor is initialized, add an action to be performed after a button is clicked.
            document.querySelector( '#get-data' ).addEventListener( 'click', async () => {
                const revisionHistory = editor.plugins.get( 'RevisionHistory' );

                // Get the document data.
                const editorData = editor.data.get();

                // Update the revisions state.
                await editor.plugins.get( 'RevisionTracker' ).update();

                // Get the revisions data (in JSON format, so it is easier to save).
                const revisionsData = revisionHistory.getRevisions( { toJSON: true } );

                // Now, use `editorData` and `revisionsData` to save the data in your application.
                //
                // Note: it is a good idea to verify the revision `creatorId` parameter when saving
                // a revision in the database. However, do not overwrite the value if it was set to `null`!
                //
                console.log( editorData );
                console.log( revisionsData );
            } );
        } )
        .catch( error => console.error( error ) );
</script>

</body>
</html>

# Demo

Console:

// Use the `Save revisions` button to see the result...

# Adapter integration

Adapter integration uses an adapter object – provided by you – to immediately save revisions data in your data store.

This is the recommended way of integrating revision history with your application as it lets you handle the client-server communication more securely. For example, you can check user permissions, validate sent data, or update the data with information obtained on the server side.

Additionally, revisions may include a significant amount of data. Loading multiple revisions of a big document may adversely impact the loading time of your application. When using an adapter, the revision data is loaded on demand, when needed. This improves the overall user experience.

It is important to load the revisions data during the plugin initialization step, that is, using the init() method of the integration plugin that you will provide (as in the example below).

This sample does not contain the comments and track changes adapters. Check the comments integration guide and track changes integration guide to learn how to build a complete solution. Also, these snippets define the same list of users. Make sure to deduplicate this code and define the list of users once to avoid errors.

# Saving document data and revisions data

Before you move to the actual implementation remember that you should always keep the document data and revisions data in sync. This means that whenever you save one, you should save the other as well. This is natural for the “load & save” integration but for adapter integration, you need to keep that in mind. A mismatch in the data will result in the feature not working correctly.

Additionally, the document data should not be further post-processed after it is saved. To be precise, it should not be changed in a way that would result in a different model after you load the document data.

# Implementation

First, define the adapter using the RevisionHistory#adapter property. The adapter methods allow you to load and save changes in your database. Read the API reference for RevisionHistoryAdapter carefully to make sure that you integrate the feature with your application correctly.

Each change in revisions is performed immediately on the UI side. However, all adapter actions are asynchronous and are performed in the background. Because of this, all adapter methods need to return a Promise. When the promise is resolved, it means that everything went fine and a local change was successfully saved in the data store. When the promise is rejected, the editor throws a CKEditorError error, which works nicely together with the watchdog feature. When you handle the server response, you can decide if the promise should be resolved or rejected.

While the adapter is saving the revision’s data, a pending action is automatically added to the editor PendingActions plugin. Thanks to this, you do not have to worry that the editor will be destroyed before the adapter action has finished.

Now you are ready to create the adapter.

// Application data will be available under a global variable `appData`
const appData = {
    // The initial editor data.
    data: `<figure class="image">
        <img src="https://ckeditor.com/docs/ckeditor5/latest/assets/img/revision-history-demo.png">
    </figure>
    <h1>PUBLISHING AGREEMENT</h1>
    <h3>Introduction</h3>
    <p>This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”.</p>
    <h3>Grant of Rights</h3>
    <p>The Author grants the Publisher full right and title to the following, in perpetuity:</p>
    <ul>
        <li>To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future.</li>
        <li>To create or devise modified, abridged, or derivative works based on the works listed.</li>
        <li>To allow others to use the listed works at their discretion, without providing additional compensation to the Author.</li>
    </ul>
    <p>These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future.</p>
    <p>Any rights not granted to the Publisher above remain with the Author.</p>
    <p>The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature.</p>`,

    // The user data.
    user: {
        id: 'u1',
        name: 'Mex Haddox'
    },

    // The ID of the current user.
    userId: 'u1'
}

// A plugin that introduces the adapter.
class RevisionHistoryAdapter {
    constructor( editor ) {
        this.editor = editor;
    }

    static get pluginName() {
        return 'RevisionHistoryAdapter';
    }

    static get requires() {
        return [ 'RevisionHistory' ];
    }

    async init() {
        const revisionHistory = this.editor.plugins.get( 'RevisionHistory' );

        revisionHistory.adapter = {
            getRevision: ( { revisionId } ) => {
                // Get the revision data based on its ID.
                // This should be an asynchronous request to your database.
                // Do not dump your revisions data here -- this is only for testing purposes.
                switch ( revisionId ) {
                    case 'initial':
                        return Promise.resolve(
                            {
                                "id": "initial",
                                "name": "Initial revision",
                                "creatorId": "u1",
                                "authorsIds": [ "u1" ],
                                "diffData": {
                                    "main": {
                                        "insertions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ………… by and between The Lower Shelf, the “Publisher”, and …………, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him/herself and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]},{"name":"p","attributes":[],"children":["Publishing formats are enumerated in Appendix A."]}]',
                                        "deletions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ………… by and between The Lower Shelf, the “Publisher”, and …………, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him/herself and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]},{"name":"p","attributes":[],"children":["Publishing formats are enumerated in Appendix A."]}]'
                                    }
                                },
                                "createdAt": "2021-05-27T13:22:59.077Z",
                                "attributes": {},
                                "fromVersion": 1,
                                "toVersion": 1
                            }
                        );
                    case 'e6f80e6be6ee6057fd5a449ab13fba25d':
                        return Promise.resolve(
                            {
                                "id": "e6f80e6be6ee6057fd5a449ab13fba25d",
                                "name": "Updated with the actual data",
                                "creatorId": "u1",
                                "authorsIds": [ "u1" ],
                                "diffData": {
                                    "main": {
                                        "insertions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ",{"name":"revision-start","attributes":[["name","insertion:u1:0"]],"children":[]},"1st",{"name":"revision-end","attributes":[["name","insertion:u1:0"]],"children":[]}," ",{"name":"revision-start","attributes":[["name","insertion:u1:1"]],"children":[]},"June 2020 ",{"name":"revision-end","attributes":[["name","insertion:u1:1"]],"children":[]},"by and between The Lower Shelf, the “Publisher”, and ",{"name":"revision-start","attributes":[["name","insertion:u1:2"]],"children":[]},"John Smith",{"name":"revision-end","attributes":[["name","insertion:u1:2"]],"children":[]},", the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]',
                                        "deletions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ",{"name":"revision-start","attributes":[["name","deletion:u1:0"]],"children":[]},"…………",{"name":"revision-end","attributes":[["name","deletion:u1:0"]],"children":[]}," by and between The Lower Shelf, the “Publisher”, and ",{"name":"revision-start","attributes":[["name","deletion:u1:1"]],"children":[]},"…………",{"name":"revision-end","attributes":[["name","deletion:u1:1"]],"children":[]},", the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him",{"name":"revision-start","attributes":[["name","deletion:u1:2"]],"children":[]},"/herself",{"name":"revision-end","attributes":[["name","deletion:u1:2"]],"children":[]}," and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature.",{"name":"revision-start","attributes":[["name","deletion:u1:3"]],"children":[]}]},{"name":"p","attributes":[],"children":["Publishing formats are enumerated in Appendix A.",{"name":"revision-end","attributes":[["name","deletion:u1:3"]],"children":[]}]}]'
                                    }
                                },
                                "createdAt": "2021-05-27T13:23:52.553Z",
                                "attributes": {},
                                "fromVersion": 1,
                                "toVersion": 20
                            }
                        );
                    case 'e6590c50ccbc86acacb7d27231ad32064':
                        return Promise.resolve(
                            {
                                "id": "e6590c50ccbc86acacb7d27231ad32064",
                                "name": "Inserted logo",
                                "creatorId": "u1",
                                "authorsIds": [ "u1" ],
                                "diffData": {
                                    "main": {
                                        "insertions": '[{"name":"figure","attributes":[["data-revision-start-before","insertion:u1:0"],["class","image"]],"children":[{"name":"img","attributes":[["src","https://ckeditor.com/docs/ckeditor5/latest/assets/img/revision-history-demo.png"]],"children":[]}]},{"name":"h1","attributes":[],"children":[{"name":"revision-end","attributes":[["name","insertion:u1:0"]],"children":[]},"PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]',
                                        "deletions": '[{"name":"h1","attributes":[["data-revision-start-before","deletion:u1:0"]],"children":[{"name":"revision-end","attributes":[["name","deletion:u1:0"]],"children":[]},"PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]'
                                    }
                                },
                                "createdAt": "2021-05-27T13:26:39.252Z",
                                "attributes": {},
                                "fromVersion": 20,
                                "toVersion": 24
                            }
                        );
                    case 'egh91t5jccbi894cacxx7dz7t36aj3k021':
                        return Promise.resolve(
                            {
                                "id": "egh91t5jccbi894cacxx7dz7t36aj3k021",
                                "name": null,
                                "creatorId": null,
                                "authorsIds": [],
                                "diffData": {
                                    "main": {
                                        "insertions": '[{"name":"figure","attributes":[["class","image"]],"children":[{"name":"img","attributes":[["src","https://ckeditor.com/docs/ckeditor5/latest/assets/img/revision-history-demo.png"]],"children":[]}]},{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]',
                                        "deletions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]'
                                    }
                                },
                                "createdAt": "2021-05-27T13:26:39.252Z",
                                "attributes": {},
                                "fromVersion": 24,
                                "toVersion": 24
                            }
                        );
                }
            },
            updateRevisions: revisionsData => {
                const documentData = this.editor.getData();

                // This should be an asynchronous request to your database
                // that saves `revisionsData` and `documentData`.
                //
                // The document data should be saved each time a revision is saved.
                //
                // `revisionsData` is an array with objects,
                // where each object contains updated and new revisions.
                //
                // See the API reference for `RevisionHistoryAdapter` to learn
                // how to correctly integrate the feature with your application.
                //
                return Promise.resolve();
            }
        };

        // Add the revisions data for existing revisions.
        // You can either dump the revisions data straight in the source code or
        // you can fetch the data asynchronously from your database (as this example shows).
        //
        // Note that the revisions data does not contain the `diffData` property.
        // The `diffData` property may be big and will be fetched on demand by `adapter.getRevision()`.
        //
        const revisionsData = await this._fetchRevisionsData();

        for ( const revisionData of revisionsData ) {
            revisionHistory.addRevisionData( revisionData );
        }
    }

    async _fetchRevisionsData() {
        // Make an asynchronous call to your database.
        return Promise.resolve(
            [
                {
                    "id": "initial",
                    "name": "Initial revision",
                    "creatorId": "u1",
                    "authorsIds": [ "u1" ],
                    "createdAt": "2021-05-27T13:22:59.077Z",
                    "attributes": {},
                    "fromVersion": 1,
                    "toVersion": 1
                },
                {
                    "id": "e6f80e6be6ee6057fd5a449ab13fba25d",
                    "name": "Updated with the actual data",
                    "creatorId": "u1",
                    "authorsIds": [ "u1" ],
                    "createdAt": "2021-05-27T13:23:52.553Z",
                    "attributes": {},
                    "fromVersion": 1,
                    "toVersion": 20
                },
                {
                    "id": "e6590c50ccbc86acacb7d27231ad32064",
                    "name": "Inserted logo",
                    "creatorId": "u1",
                    "authorsIds": [ "u1" ],
                    "createdAt": "2021-05-27T13:26:39.252Z",
                    "attributes": {},
                    "fromVersion": 20,
                    "toVersion": 24
                },
                // An empty current revision.
                {
                    "id": "egh91t5jccbi894cacxx7dz7t36aj3k021",
                    "name": null,
                    "creatorId": null,
                    "authorsIds": [],
                    "createdAt": "2021-05-27T13:26:39.252Z",
                    "attributes": {},
                    "fromVersion": 24,
                    "toVersion": 24
                }
            ]
        );
    }
}

// Initialize the users here.
class UsersInit {
    constructor( editor ) {
        this.editor = editor;
    }

    static get pluginName() {
        return 'UsersInit';
    }

    static get requires() {
        return [ 'Users' ];
    }

    init() {
        const users = this.editor.plugins.get( 'Users' );

        users.addUser( appData.user );
        users.defineMe( appData.userId );
    }
}

// To load comments and track changes,
// integrate the comments and track changes adapters.

ClassicEditor
    .create( document.querySelector( '#editor' ), {
        initialData: appData.data,
        extraPlugins: [ UsersInit, RevisionHistoryAdapter ],
        licenseKey: 'your-license-key',
        sidebar: {
            container: document.querySelector( '#sidebar-container' )
        },
        revisionHistory: {
            editorContainer: document.querySelector( '#editor-container' ),
            viewerContainer: document.querySelector( '#revision-viewer-container' ),
            viewerEditorElement: document.querySelector( '#revision-viewer-editor' ),
            viewerSidebarContainer: document.querySelector( '#revision-viewer-sidebar' )
        },
        toolbar: {
            items: [ 'bold', 'italic', '|', 'revisionHistory' ]
        }
    } )
    .catch( error => console.error( error ) );

It is recommended to stringify the revisions’ attributes property value to JSON and save it as a string in your database. Then parse the value from JSON when loading revisions.

The adapter is now ready to use with your rich text editor.

# Full implementation

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>CKEditor&nbsp;5 with revision history</title>
    <style type="text/css">
    .editor-container {
        display: flex;
        flex-direction: row;
        flex-wrap: nowrap;
        position: relative;
        width: 1260px;
    }

    #revision-viewer-container {
        display: none;
    }

    .editor-container > .ck-editor {
        position: relative;
        width: 950px;
    }

    .editor-container .ck-editor__top .ck-toolbar {
        border-top-right-radius: 0;
        border-bottom-right-radius: 0;
    }

    .editor-container .ck-editor__editable_inline {
        border-top-right-radius: 0;
        border-bottom-right-radius: 0;
    }

    .sidebar-container {
        position: relative;
        width: 600px;
        overflow: hidden;
        background: var(--ck-color-toolbar-background);
        border: 1px solid var(--ck-color-toolbar-border);
        margin-left: -1px;
    }
    </style>
</head>
<body>

<div class="editors-holder">
    <!-- The original editor. !-->
    <div id="editor-container">
        <div class="editor-container">
            <div id="editor"></div>
            <div class="sidebar-container" id="sidebar-container"></div>
        </div>
    </div>

    <!-- Structure for the revision viewer editor. !-->
    <div id="revision-viewer-container">
        <div class="editor-container">
            <div id="revision-viewer-editor"></div>
            <div class="sidebar-container" id="revision-viewer-sidebar"></div>
        </div>
    </div>
</div>
<script src="../build/ckeditor.js"></script>
<script>
    // Application data will be available under a global variable `appData`
    const appData = {
        // The initial editor data.
        data: `<figure class="image">
        <img src="https://ckeditor.com/docs/ckeditor5/latest/assets/img/revision-history-demo.png">
    </figure>
    <h1>PUBLISHING AGREEMENT</h1>
    <h3>Introduction</h3>
    <p>This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”.</p>
    <h3>Grant of Rights</h3>
    <p>The Author grants the Publisher full right and title to the following, in perpetuity:</p>
    <ul>
        <li>To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future.</li>
        <li>To create or devise modified, abridged, or derivative works based on the works listed.</li>
        <li>To allow others to use the listed works at their discretion, without providing additional compensation to the Author.</li>
    </ul>
    <p>These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future.</p>
    <p>Any rights not granted to the Publisher above remain with the Author.</p>
    <p>The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature.</p>`,

        // The user data.
        user: {
            id: 'u1',
            name: 'Mex Haddox'
        },

        // The ID of the current user.
        userId: 'u1'
    }

    // A plugin that introduces the adapter.
    class RevisionHistoryAdapter {
        constructor( editor ) {
            this.editor = editor;
        }

        static get pluginName() {
            return 'RevisionHistoryAdapter';
        }

        static get requires() {
            return [ 'RevisionHistory' ];
        }

        async init() {
            const revisionHistory = this.editor.plugins.get( 'RevisionHistory' );

            revisionHistory.adapter = {
                getRevision: ( { revisionId } ) => {
                    // Get the revision data based on its ID.
                    // This should be an asynchronous request to your database.
                    // Do not dump your revisions data here -- this is only for testing purposes.
                    switch ( revisionId ) {
                        case 'initial':
                            return Promise.resolve(
                                {
                                    "id": "initial",
                                    "name": "Initial revision",
                                    "creatorId": "u1",
                                    "authorsIds": [ "u1" ],
                                    "diffData": {
                                        "main": {
                                            "insertions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ………… by and between The Lower Shelf, the “Publisher”, and …………, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him/herself and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]},{"name":"p","attributes":[],"children":["Publishing formats are enumerated in Appendix A."]}]',
                                            "deletions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ………… by and between The Lower Shelf, the “Publisher”, and …………, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him/herself and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]},{"name":"p","attributes":[],"children":["Publishing formats are enumerated in Appendix A."]}]'
                                        }
                                    },
                                    "createdAt": "2021-05-27T13:22:59.077Z",
                                    "attributes": {},
                                    "fromVersion": 1,
                                    "toVersion": 1
                                }
                            );
                        case 'e6f80e6be6ee6057fd5a449ab13fba25d':
                            return Promise.resolve(
                                {
                                    "id": "e6f80e6be6ee6057fd5a449ab13fba25d",
                                    "name": "Updated with the actual data",
                                    "creatorId": "u1",
                                    "authorsIds": [ "u1" ],
                                    "diffData": {
                                        "main": {
                                            "insertions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ",{"name":"revision-start","attributes":[["name","insertion:u1:0"]],"children":[]},"1st",{"name":"revision-end","attributes":[["name","insertion:u1:0"]],"children":[]}," ",{"name":"revision-start","attributes":[["name","insertion:u1:1"]],"children":[]},"June 2020 ",{"name":"revision-end","attributes":[["name","insertion:u1:1"]],"children":[]},"by and between The Lower Shelf, the “Publisher”, and ",{"name":"revision-start","attributes":[["name","insertion:u1:2"]],"children":[]},"John Smith",{"name":"revision-end","attributes":[["name","insertion:u1:2"]],"children":[]},", the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]',
                                            "deletions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ",{"name":"revision-start","attributes":[["name","deletion:u1:0"]],"children":[]},"…………",{"name":"revision-end","attributes":[["name","deletion:u1:0"]],"children":[]}," by and between The Lower Shelf, the “Publisher”, and ",{"name":"revision-start","attributes":[["name","deletion:u1:1"]],"children":[]},"…………",{"name":"revision-end","attributes":[["name","deletion:u1:1"]],"children":[]},", the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him",{"name":"revision-start","attributes":[["name","deletion:u1:2"]],"children":[]},"/herself",{"name":"revision-end","attributes":[["name","deletion:u1:2"]],"children":[]}," and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature.",{"name":"revision-start","attributes":[["name","deletion:u1:3"]],"children":[]}]},{"name":"p","attributes":[],"children":["Publishing formats are enumerated in Appendix A.",{"name":"revision-end","attributes":[["name","deletion:u1:3"]],"children":[]}]}]'
                                        }
                                    },
                                    "createdAt": "2021-05-27T13:23:52.553Z",
                                    "attributes": {},
                                    "fromVersion": 1,
                                    "toVersion": 20
                                }
                            );
                        case 'e6590c50ccbc86acacb7d27231ad32064':
                            return Promise.resolve(
                                {
                                    "id": "e6590c50ccbc86acacb7d27231ad32064",
                                    "name": "Inserted logo",
                                    "creatorId": "u1",
                                    "authorsIds": [ "u1" ],
                                    "diffData": {
                                        "main": {
                                            "insertions": '[{"name":"figure","attributes":[["data-revision-start-before","insertion:u1:0"],["class","image"]],"children":[{"name":"img","attributes":[["src","https://ckeditor.com/docs/ckeditor5/latest/assets/img/revision-history-demo.png"]],"children":[]}]},{"name":"h1","attributes":[],"children":[{"name":"revision-end","attributes":[["name","insertion:u1:0"]],"children":[]},"PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]',
                                            "deletions": '[{"name":"h1","attributes":[["data-revision-start-before","deletion:u1:0"]],"children":[{"name":"revision-end","attributes":[["name","deletion:u1:0"]],"children":[]},"PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]'
                                        }
                                    },
                                    "createdAt": "2021-05-27T13:26:39.252Z",
                                    "attributes": {},
                                    "fromVersion": 20,
                                    "toVersion": 24
                                }
                            );
                        case 'egh91t5jccbi894cacxx7dz7t36aj3k021':
                            return Promise.resolve(
                                {
                                    "id": "egh91t5jccbi894cacxx7dz7t36aj3k021",
                                    "name": null,
                                    "creatorId": null,
                                    "authorsIds": [],
                                    "diffData": {
                                        "main": {
                                            "insertions": '[{"name":"figure","attributes":[["class","image"]],"children":[{"name":"img","attributes":[["src","https://ckeditor.com/docs/ckeditor5/latest/assets/img/revision-history-demo.png"]],"children":[]}]},{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]',
                                            "deletions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]'
                                        }
                                    },
                                    "createdAt": "2021-05-27T13:26:39.252Z",
                                    "attributes": {},
                                    "fromVersion": 24,
                                    "toVersion": 24
                                }
                            );
                    }
                },
                updateRevisions: revisionsData => {
                    const documentData = this.editor.getData();

                    // This should be an asynchronous request to your database
                    // that saves `revisionsData` and `documentData`.
                    //
                    // The document data should be saved each time a revision is saved.
                    //
                    // `revisionsData` is an array with objects,
                    // where each object contains updated and new revisions.
                    //
                    // See the API reference for `RevisionHistoryAdapter` to learn
                    // how to correctly integrate the feature with your application.
                    //
                    return Promise.resolve();
                }
            };

            // Add the revisions data for existing revisions.
            // You can either dump the revisions data straight in the source code or
            // you can fetch the data asynchronously from your database (as this example shows).
            //
            // Note that the revisions data does not contain the `diffData` property.
            // The `diffData` property may be big and will be fetched on demand by `adapter.getRevision()`.
            //
            const revisionsData = await this._fetchRevisionsData();

            for ( const revisionData of revisionsData ) {
                revisionHistory.addRevisionData( revisionData );
            }
        }

        async _fetchRevisionsData() {
            // Make an asynchronous call to your database.
            return Promise.resolve(
                    [
                        {
                            "id": "initial",
                            "name": "Initial revision",
                            "creatorId": "u1",
                            "authorsIds": [ "u1" ],
                            "createdAt": "2021-05-27T13:22:59.077Z",
                            "attributes": {},
                            "fromVersion": 1,
                            "toVersion": 1
                        },
                        {
                            "id": "e6f80e6be6ee6057fd5a449ab13fba25d",
                            "name": "Updated with the actual data",
                            "creatorId": "u1",
                            "authorsIds": [ "u1" ],
                            "createdAt": "2021-05-27T13:23:52.553Z",
                            "attributes": {},
                            "fromVersion": 1,
                            "toVersion": 20
                        },
                        {
                            "id": "e6590c50ccbc86acacb7d27231ad32064",
                            "name": "Inserted logo",
                            "creatorId": "u1",
                            "authorsIds": [ "u1" ],
                            "createdAt": "2021-05-27T13:26:39.252Z",
                            "attributes": {},
                            "fromVersion": 20,
                            "toVersion": 24
                        },
                        // An empty current revision.
                        {
                            "id": "egh91t5jccbi894cacxx7dz7t36aj3k021",
                            "name": null,
                            "creatorId": null,
                            "authorsIds": [],
                            "createdAt": "2021-05-27T13:26:39.252Z",
                            "attributes": {},
                            "fromVersion": 24,
                            "toVersion": 24
                        }
                    ]
            );
        }
    }

    // Initialize the users here.
    class UsersInit {
        constructor( editor ) {
            this.editor = editor;
        }

        static get pluginName() {
            return 'UsersInit';
        }

        static get requires() {
            return [ 'Users' ];
        }

        init() {
            const users = this.editor.plugins.get( 'Users' );

            users.addUser( appData.user );
            users.defineMe( appData.userId );
        }
    }

    // To load comments and track changes,
    // integrate the comments and track changes adapters.

    ClassicEditor
        .create( document.querySelector( '#editor' ), {
            initialData: appData.data,
            extraPlugins: [ UsersInit, RevisionHistoryAdapter ],
            licenseKey: 'your-license-key',
            sidebar: {
                container: document.querySelector( '#sidebar-container' )
            },
            revisionHistory: {
                editorContainer: document.querySelector( '#editor-container' ),
                viewerContainer: document.querySelector( '#revision-viewer-container' ),
                viewerEditorElement: document.querySelector( '#revision-viewer-editor' ),
                viewerSidebarContainer: document.querySelector( '#revision-viewer-sidebar' )
            },
            toolbar: {
                items: [
                    'undo', 'redo',
                    '|', 'revisionHistory', '|', 'trackChanges', 'comment',
                    '|', 'heading',
                    '|', 'fontsize', 'fontfamily', 'fontColor', 'fontBackgroundColor',
                    '-', 'bold', 'italic', 'underline',
                    '|', 'link', 'uploadImage', 'insertTable', 'blockQuote', 'codeBlock',
                    '|', 'alignment',
                    '|', 'bulletedList', 'numberedList'
                ],
                shouldNotGroupWhenFull: true
            }
        } )
        .catch( error => console.error( error ) );
</script>

</body>
</html>

# Demo

Revision history adapter actions console:

// Create new version to see the result...

# Learn more

# Pending actions

Revision history uses the pending actions feature. Pending actions are added when the revisions data is being updated through the revision history adapter, to prevent closing the editor before the update finishes.

# Editor initial data and revision data

When the revision history feature is used, it is crucial to keep the revisions state in synchronization with the document data. Because of that, if there are any revisions saved for a document, the editor initial data will be discarded. The data saved with the most recent revision will be used instead.

Since the editor’s initial data is discarded, you can lower the data load by setting the document’s initial data to empty. This applies to documents for which at least one revision was created.

If the editor initial data was set, and it was different from the revision data, a warning will be logged in the console.

# Autosave integration

If you are using the real-time collaboration feature, refer to the Autosave for revision history section in the Real-time collaboration features integration guide.

This section describes how to correctly integrate revision history with the autosave plugin. This way you can frequently save both your document data and your revisions data, and keep them in sync.

Although this guide provides ready-to-use snippets, we encourage you to also read the How revisions are saved and updated section to get a better understanding of this subject.

If the autosave plugin is enabled, the autosave callback is additionally called when the revision history view is opened.

The integration differs a bit whether you use the adapter or not.

# Autosave and “load & save” integration

Update the revision and make sure that the updated or created revision is saved together with the editor data:

autosave: {
    save: async editor => {
        const revisionTracker = editor.plugins.get( 'RevisionTracker' );

        await revisionTracker.update();

        const revisionData = revisionTracker.currentRevision.toJSON();
        const documentData = editor.getData();

        // `saveData()` should save the document and revision data in your database
        // and return a `Promise` that resolves when the save is completed.
        return saveData( documentData, revisionData );
    }
}

# Autosave and adapter integration

Integration when using the adapter is easier. Your revision adapter should save the document data as well, as was already shown in the earlier examples.

Since the adapter already takes care of saving both revision data and the document data, all that needs to be done in the autosave integration is to update the revision:

autosave: {
    save: editor => {
        const revisionTracker = editor.plugins.get( 'RevisionTracker' );

        return revisionTracker.update();
    }
}

# Advanced autosave strategies

Presented integrations will keep on updating the same revision until the user explicitly saves or names the current revision, or closes the editor.

This may result in creating a big revision, containing a lot of changes. To prevent that, autosave integration could create new revisions based on your custom strategy.

For example, you may decide to save the current revision (unsaved changes) after a chosen number of autosave callbacks since the last saved revision:

// Create a new plugin that will handle the autosave logic.
class RevisionHistoryAutosaveIntegration extends Plugin {
    init() {
        this._saveAfter = 100; // Create a new revision after 100 saves.
        this._autosaveCount = 1; // Current autosave counter.
        this._lastCreatedAt = null; // Revision `createdAt` value, when the revision was last autosaved.
    }

    async autosave() {
        const revisionTracker = this.editor.plugins.get( 'RevisionTracker' );
        const currentRevision = revisionTracker.currentRevision;

        if ( currentRevision.createdAt > this._lastCreatedAt ) {
            // Revision was saved or updated in the meantime by a different source (not autosave).
            // Reset the counter.
            this._autosaveCount = 1;
        }

        if ( this._autosaveCount === this._saveAfter ) {
            // We reached the count. Save all changes as a new revision. Reset the counter.
            await revisionTracker.saveRevision();

            this._autosaveCount = 1;
            this._lastCreatedAt = currentRevision.createdAt;
        } else {
            // Try updating the "current revision" with the new document changes.
            // If there are any new changes, the `createdAt` property will change its value.
            // Do not raise the counter, if the revision has not been updated!
            await revisionTracker.update();

            if ( currentRevision.createdAt > this._lastCreatedAt ) {
                this._autosaveCount++;
                this._lastCreatedAt = currentRevision.createdAt;
            }
        }

        return true;
    }
}

ClassicEditor
    .create( document.querySelector( '#editor' ), {
        extraPlugins: [
            // ...
            // Add the new plugin to the editor configuration:
            RevisionHistoryAutosaveIntegration
        ],
        // ...
        // Add the autosave configuration -- call the plugin method:
        autosave: {
            save: editor => {
                return editor.plugins.get( RevisionHistoryAutosaveIntegration ).autosave();
            }
        }
    } )
    .catch( error => console.error( error ) );

Similarly, you can implement a saving strategy that would include time since the last saved revision, number of operations, or multiple variables used together to decide if a new revision should be saved.

# How revisions are updated and saved

Understanding how and when revisions are updated and saved is important when it comes to writing custom code that integrates with revision history, including autosave integration.

There are always at least two revisions available for a document: the initial revision and the current revision. If the document is new and no revisions have been created for it yet, the two revisions are created when the editor is initialized.

The initial revision contains the editor data from when the document was initialized for the first time. It can be empty or contain some content. The initial revision’s ID will equal to document ID or to 'initial' if the document ID is not specified.

The current revision is a revision that stores all unsaved document changes, that is, changes that have not been saved in earlier revisions. It is always at the top of the revisions list. If a new revision is created, it will contain all unsaved changes and will be added below the current revision. Then, the current revision will be empty, until again updated with new, unsaved document changes. An empty current revision is not shown on the revisions list.

The current revision is not updated automatically when the document changes. The update can be done using the revisions feature API. It is enough to update the current revision when you need to save it, for example, in the autosave callback. The update is also triggered when the revision history view is opened. In this case, either the autosave callback is called, or the current revision is updated (if the autosave plugin is not used).

A new revision can be saved using the revisions feature API. Also, a new revision will be created when:

  • A user saves a revision using the dropdown in the editor toolbar.
  • A user gives a name to the current revision (in the revision history view).
  • Each time the editor is initialized (a new current revision will be created, while the old current revision will become a regular revision).

# Save or update revision using the feature API

const revisionTrackerPlugin = this.editor.plugins.get( 'RevisionTracker' );

// Updates the "current revision", that is, the revision containing unsaved changes.
revisionTrackerPlugin.update();

// Creates a new revision that will contain all the unsaved changes.
// See the API reference to learn more.
revisionTrackerPlugin.saveRevision();
revisionTrackerPlugin.saveRevision( { name: 'My revision' } );