Contribute to this guide

guideUpdate to CKEditor 5 v33.x

When updating your CKEditor 5 installation, make sure all the packages are the same version to avoid errors.

For custom builds, you may try removing the package-lock.json or yarn.lock files (if applicable) and reinstalling all packages before rebuilding the editor. For best results, make sure you use the most recent package versions.

# Update to CKEditor 5 v33.0.0

Released on March 9, 2022.

For the entire list of changes introduced in version 33.0.0, see the release notes for CKEditor 5 v33.0.0.

Listed below are the most important changes that require your attention when upgrading to CKEditor 5 v33.0.0.

# New import paths in the ckeditor5-list package

Starting with v33.0.0, some import paths have changed in the ckeditor5-list package. If your application imports individual plugins to integrate or build CKEditor 5, you should update the paths accordingly:

// ❌ Old import paths:
import ListEditing from '@ckeditor/ckeditor5-list/src/listediting';
import ListUI from '@ckeditor/ckeditor5-list/src/listui';
import TodoListEditing from '@ckeditor/ckeditor5-list/src/todolistediting';
import ListPropertiesEditing from '@ckeditor/ckeditor5-list/src/listpropertiesediting';

// ✅ New import paths (with subdirectories):
import ListEditing from '@ckeditor/ckeditor5-list/src/list/listediting';
import ListUI from '@ckeditor/ckeditor5-list/src/list/listui';
import TodoListEditing from '@ckeditor/ckeditor5-list/src/todolist/todolistediting';
import ListPropertiesEditing from '@ckeditor/ckeditor5-list/src/listproperties/listpropertiesediting';

Import paths for top-level plugins such as List, ListProperties, TodoList, etc. remain the same. If you are not sure which import path you should use, you can always browse the GitHub source code that corresponds to the contents of the package on npm.

# Additional dependencies in CKEditor 5 collaboration features

We introduced the DLL builds support for collaboration features. As a result, some imports, plugin requirements, and cross-package dependencies have changed to allow for the new building process.

From now on, extra plugins will be required when you add the following CKEditor 5 collaboration features to the editor:

  • TrackChanges will also require adding Comments to the list of the editor plugins:

    // ❌ Old imports:
    import TrackChanges from '@ckeditor/ckeditor5-track-changes/src/trackchanges';
    // ✅ New imports:
    import TrackChanges from '@ckeditor/ckeditor5-track-changes/src/trackchanges';
    import Comments from '@ckeditor/ckeditor5-comments/src/comments';
    
  • RealTimeCollaborativeEditing will also require CloudServices:

    // ❌ Old imports:
    import RealTimeCollaborativeEditing from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativeediting';
    // ✅ New imports:
    import RealTimeCollaborativeEditing from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativeediting';
    import CloudServices from '@ckeditor/ckeditor5-cloud-services/src/cloudservices';
    
  • RealTimeCollaborativeComments will also require CloudServices and Comments:

    // ❌ Old imports:
    import RealTimeCollaborativeComments from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativecomments';
    // ✅ New imports:
    import RealTimeCollaborativeComments from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativecomments';
    import CloudServices from '@ckeditor/ckeditor5-cloud-services/src/cloudservices';
    import Comments from '@ckeditor/ckeditor5-comments/src/comments';
    
  • RealTimeCollaborativeTrackChanges will also require CloudServices, Comments, and TrackChanges:

    // ❌ Old imports:
    import RealTimeCollaborativeTrackChanges from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativetrackchanges';
    // ✅ New imports:
    import RealTimeCollaborativeTrackChanges from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativetrackchanges';
    import CloudServices from '@ckeditor/ckeditor5-cloud-services/src/cloudservices';
    import Comments from '@ckeditor/ckeditor5-comments/src/comments';
    import TrackChanges from '@ckeditor/ckeditor5-track-changes/src/trackchanges';
    
  • RealTimeCollaborativeRevisionHistory will also require CloudServices:

    // ❌ Old imports:
    import RealTimeCollaborativeRevisionHistory from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativerevisionhistory';
    // ✅ New imports:
    import RealTimeCollaborativeRevisionHistory from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativerevisionhistory';
    import CloudServices from '@ckeditor/ckeditor5-cloud-services/src/cloudservices';
    
  • CloudServicesCommentsAdapter will also require CloudServices and CommentsRepository:

    // ❌ Old imports:
    import CloudServicesCommentsAdapter from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativecomments/cloudservicescommentsadapter';
    // ✅ New imports:
    import CloudServicesCommentsAdapter from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativecomments/cloudservicescommentsadapter';
    import CloudServices from '@ckeditor/ckeditor5-cloud-services/src/cloudservices';
    import CommentsRepository from '@ckeditor/ckeditor5-comments/src/comments/commentsrepository';
    
  • CloudServicesTrackChangesAdapter will also require CloudServices, CommentsRepository, and TrackChangesEditing:

    // ❌ Old imports:
    import CloudServicesTrackChangesAdapter from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativetrackchanges/cloudservicestrackchangesadapter';
    
    // ✅ New imports:
    import CloudServicesTrackChangesAdapter from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativetrackchanges/cloudservicestrackchangesadapter';
    import CloudServices from '@ckeditor/ckeditor5-cloud-services/src/cloudservices';
    import CommentsRepository from '@ckeditor/ckeditor5-comments/src/comments/commentsrepository';
    import TrackChangesEditing from '@ckeditor/ckeditor5-track-changes/src/trackchangesediting';
    
  • CloudServicesRevisionHistoryAdapter will also require CloudServices:

    // ❌ Old imports:
    import CloudServicesRevisionHistoryAdapter from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativerevisionhistory/cloudservicesrevisionhistoryadapter';
    
    // ✅ New imports:
    import CloudServicesRevisionHistoryAdapter from '@ckeditor/ckeditor5-real-time-collaboration/src/realtimecollaborativerevisionhistory/cloudservicesrevisionhistoryadapter';
    import CloudServices from '@ckeditor/ckeditor5-cloud-services/src/cloudservices';
    

# Mandatory consumption of all model items in the downcast conversion pipeline

Starting with v33.0.0, all items in the model must be consumed in the downcast conversion pipeline to prevent errors and unpredictable behavior of the editor features. If a model item is not consumed, the conversion-model-consumable-not-consumed error will be thrown. To learn more about the causes of this error and possible solutions, refer to the API documentation.

# The triggerBy option in the downcast pipeline is now obsolete

The v33.0.0 release introduces a massive upgrade to the conversion system. You can find a detailed summary of all these changes in the developer notes on GitHub.

If some of your downcast pipeline converters used the experimental triggerBy property to trigger (re)conversion upon changes of attributes or children, you need to update them. For instance:

// ❌ The old conversion using obsolete "triggerBy":
editor.conversion.for( 'downcast' ).elementToElement( {
    model: 'myElement',
    view: ( modelElement, { writer } ) => {
        return writer.createContainerElement( 'div', {
            'data-owner-id': modelElement.getAttribute( 'ownerId' ),
            class: `my-element my-element-${ modelElement.getAttribute( 'type' ) }`
        } );
    },
    triggerBy: {
        attributes: [ 'ownerId', 'type' ],
        children: 'childModelElement'
    }
} );

// ✅ The new conversion syntax:
editor.conversion.for( 'downcast' ).elementToElement( {
    model: {
        name: 'myElement',
        attributes: [ 'ownerId', 'type' ],
        children: true
    },
    view: ( modelElement, { writer } ) => {
        // The same converter code.
    }
} );

The new syntax is available both in elementToElement and elementToStructure helpers.

The conversion brought by the TableEditing plugin was refined in this version and now relies heavily on the elementToStructure downcast conversion helper.

If your integration extends or overwrites that conversion (the table, tableRow, tableCell model elements and/or their attributes), you might need to undertake some actions to align your custom features with the latest editor API. The extent of necessary changes may vary depending on how advanced your customizations are.

# Responsibility shift in the low–level downcast converters

Downcast dispatcher will now fire events for model items no matter if they were consumed or not. This means that the low–level (event-driven) downcast converters listening to these events must first test whether the item has already been consumed to prevent double conversion and errors:

editor.conversion.for( 'downcast' ).add( dispatcher => {
    dispatcher.on( '...', ( evt, data, conversionApi ) => {
        // Before converting, check whether the change has not been consumed yet.
        if ( !conversionApi.consumable.test( data.item, evt.name ) ) {
            return;
        }

        // Converter code...
    } );
} );

Also, starting with CKEditor 5 v33.0.0, your custom converters must consume all model items to prevent further errors.

# The Differ#refreshItem() method is now obsolete

Differ#refreshItem() is obsolete and was replaced by reconvertItem:

// ❌ Old API:
editor.model.document.differ.refreshItem( ... );

// ✅ New API:
editor.editing.reconvertItem( ... );

# Comments editor configuration is now required

Since we removed the cross-package dependencies inside the project, the configuration for the comments editor became required. The editor used in the comments section is also a CKEditor 5 instance. It is configured the same way as the regular editor.

After the update, you should configure the comments editor using the config.comments.editorConfig option:

import Autoformat from '@ckeditor/ckeditor5-autoformat/src/autoformat';
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
import List from '@ckeditor/ckeditor5-list/src/list';

ClassicEditor.create( document.querySelector( '#editor' ), {
    // ...

    comments: {
        editorConfig: {
            extraPlugins: [ Autoformat, Bold, Italic, List ]
        }
    }
} );

Before the change, the comments editor included the Bold, Italic, Autoformat, and List plugins.

If you want to keep the same user experience after updating the editor, you need to configure the comments editor as shown in the example above.

If this configuration is not provided, a warning will be logged in the console and the comments editor will be initialized with the most basic features: typing, paragraph, and undo.

To hide the warning (and use the basic configuration), provide an empty configuration for the comments editor:

ClassicEditor.create( document.querySelector( '#editor' ), {
    // ...

    comments: {
        editorConfig: {}
    }
} );

# New API

# New elementToStructure() downcast helper

The new elementToStructure helper was introduced to streamline downcast conversion to complex view structures. Unlike elementToElement, it allows placing children of an element in configurable slots in the view structure without the need to develop complex converters using the low–level event–driven API.

To learn more about this new helper, refer to the API documentation or check out the official conversion guide with plenty of examples and details.

# New API to trigger downcast (re)conversion

The triggerBy property is obsolete and a new API was created to trigger the downcast conversion of a model element upon changes to its attributes or children (also known as reconversion):

editor.conversion.for( 'downcast' ).elementToElement( {
    model: {
        name: 'myElement',

        // Changes to these attributes will (re)convert myElement.
        attributes: [ 'ownerId', 'type' ],

        // If some children are added or removed, myElement will be (re)converted.
        children: true
    },
    view: ( modelElement, { writer } ) => {
        // ...
    }
} );

The new syntax of the model property is available in the elementToElement and elementToStructure helpers. Refer to the respective API documentation for more details.

# Improved API of DowncastWriter#createContainerElement()

Starting from v33.0.0, you can specify the children of a container element directly in the createContainerElement method:

// ❌ Old API:
const element = writer.createContainerElement( 'p', { id: '1234' } );

writer.insert( writer.createPositionAt( element, 0 ), childElementA );
writer.insert( writer.createPositionAt( element, 1 ), childElementB );
// ...

// ✅ New API:
writer.createContainerElement( 'p', { id: '1234' }, [
    childElementA,
    childElementB,

    // ...
] );