Content for header part.
', content: 'Content for main part.
', leftSide: 'Content for left-side box.
', rightSide: 'Content for right-side box.
' } ); ``` Setting the data through `config.roots.Content for header part.
', element: document.querySelector( '#header' ) }, content: { initialData: 'Content for main part.
', element: document.querySelector( '#content' ) }, leftSide: { initialData: 'Content for left-side box.
', element: document.querySelector( '#left-side' ) }, rightSide: { initialData: 'Content for right-side box.
', element: document.querySelector( '#right-side' ) } } } ); ``` Specify root name when obtaining the data ``` editor.getData( { rootName: 'leftSide' } ); // -> 'Content for left-side box.
' ``` Learn more about using the multi-root editor in its [API documentation](../../api/module%5Feditor-multi-root%5Fmultirooteditor-MultiRootEditor.html). source file: "ckeditor5/latest/examples/framework/bottom-toolbar-editor.html" ## Editor with a bottom toolbar and button grouping The following custom editor example showcases an editor instance with the main toolbar displayed at the bottom of the editing window. To make it possible, this example uses the [DecoupledEditor](../../api/module%5Feditor-decoupled%5Fdecouplededitor-DecoupledEditor.html) with the [main toolbar](../../api/module%5Feditor-decoupled%5Fdecouplededitoruiview-DecoupledEditorUIView.html#member-toolbar) injected after the editing root into the DOM. Learn more about the [decoupled UI in CKEditor 5](#ckeditor5/latest/framework/deep-dive/ui/document-editor.html) to find out the details of this process. Additionally, thanks to the flexibility offered by the [CKEditor 5 UI framework](#ckeditor5/latest/framework/architecture/ui-library.html), the main toolbar has been uncluttered by moving buttons related to text formatting into the custom “Formatting options” dropdown. All remaining dropdown and (button) tooltips have been tuned to open upward for the best user experience. Similar effect can also be achieved by using the [built-in toolbar grouping option](#ckeditor5/latest/getting-started/setup/toolbar.html--grouping-toolbar-items-in-dropdowns-nested-toolbars). The presented combination of the UI and editor’s features works best for integrations where text creation comes first and formatting is applied occasionally. Some examples are email applications, (forum) post editors, chats, or instant messaging. You can probably recognize this UI setup from popular applications such as Gmail, Slack, or Zendesk. ### Editor example configuration View editor configuration script ``` import { DecoupledEditor, Plugin, Alignment, Autoformat, Bold, Italic, Strikethrough, Subscript, Superscript, Underline, BlockQuote, clickOutsideHandler, Essentials, Font, Heading, HorizontalLine, Image, ImageCaption, ImageResize, ImageStyle, ImageToolbar, ImageUpload, Indent, Link, List, MediaEmbed, Paragraph, RemoveFormat, Table, TableToolbar, DropdownButtonView, DropdownPanelView, DropdownView, ToolbarView, IconFontColor, registerIcon } from 'ckeditor5'; const fontColorIcon =/* #__PURE__ */ registerIcon( 'fontColor', IconFontColor ); class FormattingOptions extends Plugin { /** * @inheritDoc */ static get pluginName() { return 'FormattingOptions'; } /** * @inheritDoc */ constructor( editor ) { super( editor ); editor.ui.componentFactory.add( 'formattingOptions', locale => { const t = locale.t; const buttonView = new DropdownButtonView( locale ); const panelView = new DropdownPanelView( locale ); const dropdownView = new DropdownView( locale, buttonView, panelView ); const toolbarView = this.toolbarView = dropdownView.toolbarView = new ToolbarView( locale ); // Accessibility: Give the toolbar a human-readable ARIA label. toolbarView.set( { ariaLabel: t( 'Formatting options toolbar' ) } ); // Accessibility: Give the dropdown a human-readable ARIA label. dropdownView.set( { label: t( 'Formatting options' ) } ); // Toolbars in dropdowns need specific styling, hence the class. dropdownView.extendTemplate( { attributes: { class: [ 'ck-toolbar-dropdown' ] } } ); // Accessibility: If the dropdown panel is already open, the arrow down key should focus the first child of the #panelView. dropdownView.keystrokes.set( 'arrowdown', ( data, cancel ) => { if ( dropdownView.isOpen ) { toolbarView.focus(); cancel(); } } ); // Accessibility: If the dropdown panel is already open, the arrow up key should focus the last child of the #panelView. dropdownView.keystrokes.set( 'arrowup', ( data, cancel ) => { if ( dropdownView.isOpen ) { toolbarView.focusLast(); cancel(); } } ); // The formatting options should not close when the user clicked: // * the dropdown or it contents, // * any editing root, // * any floating UI in the "body" collection // It should close, for instance, when another (main) toolbar button was pressed, though. dropdownView.on( 'render', () => { clickOutsideHandler( { emitter: dropdownView, activator: () => dropdownView.isOpen, callback: () => { dropdownView.isOpen = false; }, contextElements: [ dropdownView.element, ...[ ...editor.ui.getEditableElementsNames() ].map( name => editor.ui.getEditableElement( name ) ), document.querySelector( '.ck-body-wrapper' ) ] } ); } ); // The main button of the dropdown should be bound to the state of the dropdown. buttonView.bind( 'isOn' ).to( dropdownView, 'isOpen' ); buttonView.bind( 'isEnabled' ).to( dropdownView ); // Using the font color icon to visually represent the formatting. buttonView.set( { tooltip: t( 'Formatting options' ), icon: fontColorIcon() } ); dropdownView.panelView.children.add( toolbarView ); toolbarView.fillFromConfig( editor.config.get( 'formattingOptions' ), editor.ui.componentFactory ); return dropdownView; } ); } } DecoupledEditor .create( { root: { element: document.querySelector( '#editor-content' ), }, licenseKey: 'GPL', // Or '
Lorem ipsum dolor sit amet...
', type: 'html' } }, { id: 'text4', type: 'text', label: 'Internal note (fetched on demand)', // Note: Since the `data` property is not provided, the content will be retrieved using the `getData()` callback (see below). // This will prevent fetching large content along with the list of resources. }, // URLs to resources. { id: 'url2', type: 'web-resource', label: 'Company brochure in PDF', data: 'https://example.com/brochure.pdf' }, { id: 'url3', type: 'web-resource', label: 'Company website in HTML', data: 'https://example.com/index.html' }, // ... ], // The optional callback to retrieve the content of resources without the `data` property provided by the `getResources()` callback. // When the user picks a specific resource, the content will be fetched on demand (from database or external API) by this callback. // This prevents fetching large resources along with the list of resources. getData: ( id ) => fetchDocumentContent( id ) }, // More context providers... ] }, } } } ) .then( /* ... */ ) .catch( /* ... */ ); ``` #### Automatically add selection to context By default, the editor selection is added to the AI Chat context only when the user explicitly clicks the “Ask AI” button. You can change this behavior by enabling the [config.ai.chat.context.alwaysAddSelection](../../api/module%5Fai%5Faichat%5Fmodel%5Faichatcontext-AIChatContextConfig.html#member-alwaysAddSelection) option. When set to `true`, the current editor selection is automatically added to the conversation context whenever the user changes their selection. If the selection becomes collapsed (empty), it is automatically removed from the context. This option requires the [document](../../api/module%5Fai%5Faichat%5Fmodel%5Faichatcontext-AIChatContextConfig.html#member-document) context to be enabled (which is the default). ``` ClassicEditor .create( document.querySelector( '#editor' ), { /* ... */ plugins: [ AIChat, AIEditorIntegration, /* ... */ ], ai: { chat: { context: { alwaysAddSelection: true } } } } ) .then( /* ... */ ) .catch( /* ... */ ); ``` #### Adding context via custom context provider It’s also possible to allow for adding the context using a custom external UI, for example, a file manager. First, using the [config.ai.chat.context.customItems](../../api/module%5Fai%5Faichat%5Fmodel%5Faichatcontext-AIChatContextConfig.html#member-customItems) configuration option, add a button to the “Add context” dropdown that upon pressing will execute the configured [callback](../../api/module%5Fai%5Faichat%5Fmodel%5Faichatcontext-AIContextCustomItem.html#member-callback) (for example, open custom content manager). Then you can use the [AIChatContext class API](../../api/module%5Fai%5Faichat%5Fmodel%5Faichatcontext-AIChatContext.html) to attach the resource chosen by the user to the conversation context. There are various API methods to use depending on the context type. See an example code below. ``` ClassicEditor .create( { /* ... */ plugins: [ AIChat, AIEditorIntegration, /* ... */ ], ai: { chat: { context: { // Other configuration options... customItems: [ { id: 'open-file-manager', // Unique item ID. label: 'Open file manager', // Button label displayed in the dropdown. icon: IconImage, // Icon displayed next to the label. callback: ( editor: Editor ) => { // `openFileManager()` is provided by you. It opens a custom UI, and returns a promise. // After the user finishes choosing files, the `openFileManager()` promise resolves with data of these files. openFileManager().then( chosenFiles => { editor.plugins.get( 'AIChatController' ).activeConversation.chatContext.addFilesToContext( chosenFiles ); } ); } }, { id: 'open-url-manager', // Unique item ID. label: 'Open URL manager', // Button label displayed in the dropdown. icon: IconURL, // Icon displayed next to the label. callback: ( editor: Editor ) => { // `openURLManager()` is provided by you. It opens a custom UI, and returns a promise. // After the user finishes choosing a URL, the `openURLManager()` promise resolves with proper data. openURLManager().then( chosenURL => { editor.plugins.get( 'AIChatController' ).activeConversation.chatContext.addURLToContext( chosenURL ); } ); } } ] }, } } } ) .then( /* ... */ ) .catch( /* ... */ ); ``` ### Welcome message The AI Chat feature allows you to customize the welcome message displayed to users when the chat initializes. You can set the [config.ai.chat.welcomeMessage](../../api/module%5Fai%5Faichat%5Faichat-AIChatConfig.html#member-welcomeMessage) option in your editor configuration to provide a custom message. If this option is not set, a default welcome message will be shown. ``` ClassicEditor.create( { /* ... */ plugins: [ AIChat, AIEditorIntegration, /* ... */ ], ai: { chat: { welcomeMessage: 'Hello! How can I assist you today?' // More configuration options... } } } ) .then( /* ... */ ) .catch( /* ... */ ); ``` ### Working with AI-generated changes If you ask the AI for changes to your document, for instance, _“Bold key facts in the document”_, you will receive a series of proposed changes instead of plain text responses: Move your cursor over any change to highlight the section of your document it applies to, helping you identify it among other proposed edits. #### Showing details You can toggle details of the changes by pressing the “Show details” button. By default, you will see detailed information on what exactly was suggested, including additions (green markers), removals (red markers), and formatting changes (blue markers). Click the button again to see a clean, simplified overview of the changes as they’ll appear in your document once accepted. #### Previewing changes Click on the item in the list to display the information window about an individual change with options to [apply it](#ckeditor5/latest/features/ai/ckeditor-ai-chat.html--applying-changes), [turn it into a Track Changes suggestion](#ckeditor5/latest/features/ai/ckeditor-ai-chat.html--inserting-track-changes-suggestions), or [reject it](#ckeditor5/latest/features/ai/ckeditor-ai-chat.html--rejecting-suggestions). You can use this window to browse all proposed changes and work with them one by one. As you navigate through the changes, the window will automatically follow the corresponding sections of the document. #### Applying changes Each suggestion on the list comes with an “Apply” button that allows you to apply the change to the document immediately. Click the “Apply all” button in chat to apply all AI suggestions at once. #### Inserting Track Changes suggestions [When Track Changes feature is available in your integration](#ckeditor5/latest/features/ai/ckeditor-ai-integration.html--track-changes-dependency), the “Add as suggestion” button will be available in chat. Clicking it will create a Track Changes suggestion that can later be reviewed or discarded. You can pick the “Suggest all” option under the list to turn all changes suggested by AI into Track Changes suggestions. #### Rejecting suggestions You can click the “Reject change” button to reject AI suggestions you do not want before applying the remaining ones or turning them into Track Changes suggestions. ### Chat history All your past conversations appear in the Chat history . Click the button to open the list, where you can reopen, rename, or delete any conversation. Conversations are grouped by date to help you navigate your project easily. You can filter conversations by name using the search field at the top of the user interface. ### Chat Shortcuts The AI Chat feature can be enhanced by AI Chat Shortcuts – customizable actions that help users trigger common or useful prompts with a single click. These shortcuts appear at the start of a new conversation, making it faster for users to ask questions, request summaries, check grammar, and more. AI Chat Shortcuts require loading the [AIChatShortcuts](../../api/module%5Fai%5Faichatshortcuts%5Faichatshortcuts-AIChatShortcuts.html) plugin in your editor configuration. You can configure which shortcuts are available using the [config.ai.chat.shortcuts](../../api/module%5Fai%5Faichat%5Faichat-AIChatConfig.html#member-shortcuts) option. This allows you to define shortcut labels, icons, prompts, and the type of action to execute. Shortcuts streamline repetitive queries and encourage best practices in your writing workflows. Example configuration: ``` import { AIChat, AIChatShortcuts, AIEditorIntegration, /* ... */ } from 'ckeditor5-premium-features'; ClassicEditor.create( { /* ... */ // Adding the AIChatShortcuts plugin to enable the feature. plugins: [ AIChat, AIChatShortcuts, AIEditorIntegration, /* ... */ ], /* ... */ ai: { chat: { shortcuts: [ // This shortcut runs an AI Chat prompt with Reasoning and // Web Search features turned on. { id: 'continue-writing', type: 'chat', label: 'Continue writing', prompt: 'Continue writing this document. Match the existing tone, vocabulary level, and formatting. ' + 'Do not repeat or summarize earlier sections. Ensure logical flow and progression of ideas. ' + 'Add approximately 3 paragraphs.', useReasoning: true, useWebSearch: true }, // This shortcut starts proofreading the document by the AI Review feature. { id: 'fix-grammar-and-spelling', type: 'review', label: 'Fix grammar and spelling', commandId: 'correctness' }, // This shortcut switches the UI to the Translate feature and allows // the user decide what to do next (choose a language). { id: 'translate-document', type: 'translate', label: 'Translate document' } ] } } } ) .then( /* ... */ ) .catch( /* ... */ ); ``` ### Common API AI Chat can be controlled programmatically – send messages, start conversations, and manage chat context from code. | API | Description | | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | | [AIChatController#sendMessage()](../../api/module%5Fai%5Faichat%5Faichatcontroller-AIChatController.html#function-sendMessage) | Programmatically send a message to AI Chat. | | [AIChatController#startConversation()](../../api/module%5Fai%5Faichat%5Faichatcontroller-AIChatController.html#function-startConversation) | Start a new chat conversation. | | [AIChatController#addSelectionToChatContext()](../../api/module%5Fai%5Faichat%5Faichatcontroller-AIChatController.html#function-addSelectionToChatContext) | Attach the current editor selection as context for the next message. | See the [programmatic usage guide](#ckeditor5/latest/features/ai/ckeditor-ai-programmatic.html--chat) for details, examples, and a live demo. #### REST API AI Chat conversations are also available via the [Conversations REST API](#cs/latest/guides/ckeditor-ai/conversations.html), which supports multi-turn conversation history, file uploads, and web search capabilities. Use this to build chat-based AI features outside the editor. See the [programmatic documentation](#ckeditor5/latest/features/ai/ckeditor-ai-programmatic.html) for examples and the [full API reference](https://ai.cke-cs.com). source file: "ckeditor5/latest/features/ai/ckeditor-ai-deployment.html" ## Deployment options CKEditor AI backend is available in two deployment modes: **Cloud (SaaS)** and **On-premises**. Both options provide the same core AI features – [Chat](#ckeditor5/latest/features/ai/ckeditor-ai-chat.html), [Quick Actions](#ckeditor5/latest/features/ai/ckeditor-ai-actions.html), [Review](#ckeditor5/latest/features/ai/ckeditor-ai-review.html), and [Translate](#ckeditor5/latest/features/ai/ckeditor-ai-translate.html) – with the on-premises version offering additional capabilities such as custom AI models and [MCP support](#ckeditor5/latest/features/ai/ckeditor-ai-mcp.html). ### Cloud (SaaS) The Cloud (SaaS) deployment offers the fastest way to get started with CKEditor AI. The AI service is hosted and managed by CKEditor, so there is no server-side setup required on your end. You only need to provide a valid license key and configure the editor-side plugins as described in the [integration guide](#ckeditor5/latest/features/ai/ckeditor-ai-integration.html). For more information about the Cloud AI service, refer to the [CKEditor AI Cloud Services documentation](#cs/latest/guides/ckeditor-ai/overview.html). ### On-premises The on-premises deployment allows you to run the CKEditor AI service on your own infrastructure, including private cloud environments. The service is distributed as Docker images compatible with standard container runtimes. On-premises deployment gives you full control over the AI service, including the ability to use custom AI models and providers, and to extend CKEditor AI with custom tools via [MCP (Model Context Protocol)](#ckeditor5/latest/features/ai/ckeditor-ai-mcp.html). For detailed setup instructions, requirements, and configuration, refer to the [CKEditor AI On-Premises documentation](#cs/latest/onpremises/ckeditor-ai-onpremises/overview.html). #### Connecting the editor to an on-premises service To point the editor to your on-premises AI service, set the [config.ai.serviceUrl](../../api/module%5Fai%5Faiconfig-AIConfig.html#member-serviceUrl) property to the URL of your on-premises instance: ``` ClassicEditor .create( { licenseKey: 'Lorem ipsum dolor sit amet...
', type: 'html' } }, { id: 'text4', type: 'text', label: 'Internal note (fetched on demand)', // Note: Since the `data` property is not provided, the content will be retrieved using the `getData()` callback (see below). // This will prevent fetching large content along with the list of resources. }, // URLs to resources in different formats { id: 'url1', type: 'web-resource', label: 'Blog post in Markdown', data: 'https://example.com/blog-post.md' }, { id: 'url2', type: 'web-resource', label: 'Company brochure in PDF', data: 'https://example.com/brochure.pdf' }, { id: 'url3', type: 'web-resource', label: 'Company website in HTML', data: 'https://example.com/index.html' }, { id: 'url4', type: 'web-resource', label: 'Terms of service in plain text', data: 'https://example.com/terms-of-service.txt' }, // ... ], // The optional callback to retrieve the content of resources without the `data` property provided by the `getResources()` callback. // When the user picks a specific resource, the content will be fetched on demand (from database or external API) by this callback. // This prevents fetching large resources along with the list of resources. getData: ( id ) => fetchDocumentContent( id ) }, // More context providers... ] } }, // (Optional) The configuration for AI models used across all AI features (Chat and Review Mode). models: { defaultModelId: 'gpt-5', displayedModels: [ 'gpt', 'claude' ], showModelSelector: true }, // (Optional) Configure the AI Quick Actions feature by adding a new command. quickActions: { extraCommands: [ // An action that opens the AI Chat interface for interactive conversations. { id: 'explain-like-i-am-five', displayedPrompt: 'Explain like I am five', prompt: 'Explain the following text like I am five years old.', type: 'CHAT' }, // ... More custom actions ... ], }, } } ) .then( /* ... */ ) .catch( /* ... */ ); ``` ### Configuration #### Supported AI models CKEditor AI supports OpenAI, Anthropic, and Gemini AI models. By default, the automatically selected model will be used for optimal cost and performance. You can configure the list of available models and the default model using the unified [model configuration](#ckeditor5/latest/features/ai/ckeditor-ai-chat.html--configuration), which applies to all AI features (Chat and Review Mode). Here’s a detailed list of available models with their capabilities: | **Model** | **Description** | [Web Search](#ckeditor5/latest/features/ai/ckeditor-ai-chat.html--web-search) | [Reasoning](#ckeditor5/latest/features/ai/ckeditor-ai-chat.html--reasoning) | [Configuration id](#ckeditor5/latest/features/ai/ckeditor-ai-chat.html--configuration) | | --------------------- | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | **Auto (default)** | Automatically selects best model for speed, quality, and cost. | Yes | Yes | 'auto' (also 'agent-1', learn more about [compatibility versions](#cs/latest/guides/ckeditor-ai/models.html--model-compatibility-versions)) | | **GPT-5.4** | OpenAI’s flagship model for advanced reasoning, creativity, and complex tasks | Yes | Yes | 'gpt-5.4' | | **GPT-5.2** | OpenAI’s flagship model for advanced reasoning, creativity, and complex tasks | Yes | Yes | 'gpt-5.2' | | **GPT-5.1** | OpenAI’s flagship model for advanced reasoning, creativity, and complex tasks | Yes | Yes | 'gpt-5.1' | | **GPT-5** | OpenAI’s flagship model for advanced reasoning, creativity, and complex tasks | Yes | Yes | 'gpt-5' | | **GPT-5 Mini** | A lightweight version of GPT-5 – faster, more cost-efficient | Yes | Yes | 'gpt-5-mini' | | **Claude 4.6 Sonnet** | Advanced model with improved creativity, reliability, and reasoning | Yes | Yes | 'claude-4-6-sonnet' | | **Claude 4.5 Haiku** | Cost-efficient model for quick interactions with improved reasoning | Yes | Yes | 'claude-4-5-haiku' | | **Claude 4.5 Sonnet** | Advanced model with improved creativity, reliability, and reasoning | Yes | Yes | 'claude-4-5-sonnet' | | **Gemini 3.1 Pro** | Google’s advanced model for versatile problem-solving and research | Yes | Yes | 'gemini-3-1-pro' | | **Gemini 3 Flash** | Lightweight Gemini model for fast, cost-efficient interactions | Yes | Yes | 'gemini-3-flash' | | **Gemini 2.5 Flash** | Lightweight Gemini model for fast, cost-efficient interactions | Yes | Yes | 'gemini-2-5-flash' | | **GPT-4.1** | OpenAI’s model for reliable reasoning, speed, and versatility | Yes | No | 'gpt-4.1' | | **GPT-4.1 Mini** | A lighter variant of GPT-4.1 that balances speed and cost while maintaining solid accuracy | Yes | No | 'gpt-4.1-mini' | We intend to expand this list of supported agents further with time. The [on-premises version](#ckeditor5/latest/features/ai/ckeditor-ai-deployment.html) also supports custom models and your own API keys. [Share your feedback on model availability](https://ckeditor.com/contact/). #### Cloud version endpoint While using the cloud version of the CKEditor AI feature, you need to provide the service endpoint. ``` ai: { serviceUrl: 'https://ai.cke-cs.com/v1' } ``` If you are using the EU cloud region, remember to adjust the endpoint: ``` ai: { serviceUrl: 'https://ai.cke-cs-eu.com/v1' } ``` #### Document ID The [config.collaboration.channelId](../../api/module%5Fcollaboration-core%5Fconfig-RealTimeCollaborationConfig.html#member-channelId) configuration serves as the document identifier corresponding to the edited resource (article, document, etc.) in your application. This ID is essential for maintaining [Chat](#ckeditor5/latest/features/ai/ckeditor-ai-chat.html) history, ensuring that AI conversations are properly associated with the specific document being edited. When users interact with AI features, their chat history is preserved and linked to this document ID. ``` ClassicEditor .create( { /* ... */ collaboration: { channelId: 'DOCUMENT_ID' }, /* ... */ } ) .then( /* ... */ ) .catch( /* ... */ ); ``` #### Track Changes dependency CKEditor AI can leverage the TrackChanges plugin to enhance the user experience, for instance, by allowing users to turn AI-generated content into suggestions that can later be reviewed, accepted, or rejected. Without the TrackChanges plugin, the CKEditor AI will work, but some functionalities may be limited. For the most complete integration, we highly recommend using TrackChanges along with CKEditor AI. #### UI types and positioning CKEditor AI gives you flexible options for displaying the AI user interface. The [config.ai.container](../../api/module%5Fai%5Faiconfig-AIConfig.html#member-container) property allows you to choose from three different UI placement modes: ##### Sidebar When in [AIContainerSidebar](../../api/module%5Fai%5Faiconfig-AIContainerSidebar.html) mode, the AI user interface is displayed in a specific DOM element, allowing you to inject it into your existing user interface. ``` ClassicEditor .create( { // ... Other configuration options ... ai: { container: { type: 'sidebar', // Existing DOM element to use as the container for the AI user interface. element: document.querySelector( '#ai-sidebar-container' ) // (Optional) The preferred side for positioning the tab buttons. side: 'right' }, } } ) .then( /* ... */ ) .catch( /* ... */ ); ``` In addition to the above, we recommend using the following or similar CSS to style the sidebar container for the AI user interface (tabs) to render optimally: ``` #ai-sidebar-container .ck.ck-ai-tabs { /* An arbitrary fixed width to limit the space consumed by the AI tabs. */ width: 500px; /* A fixed height that enables vertical scrolling (e.g., in the AI Chat feed). */ height: 800px; } ``` ##### Overlay When in [AIContainerOverlay](../../api/module%5Fai%5Faiconfig-AIContainerOverlay.html) mode, the AI user interface is displayed on top of the page, allowing you to position it on your preferred side. This mode is best suited for integrations with limited space. ``` ClassicEditor .create( { // ... Other configuration options ... ai: { container: { type: 'overlay', side: 'right' }, } } ) .then( /* ... */ ) .catch( /* ... */ ); ``` Learn how to [toggle the AI overlay](#ckeditor5/latest/features/ai/ckeditor-ai-integration.html--toggling-the-ui) using a dedicated toolbar button. ##### Custom When in [AIContainerCustom](../../api/module%5Fai%5Faiconfig-AIContainerCustom.html) mode, the AI user interface is displayed in a custom way, allowing you to use the building blocks of the AI user interface to create your own and satisfy the specific needs of your application. ``` ClassicEditor .create( { // ... Other configuration options ... ai: { container: { type: 'custom' }, } } ) // A custom integration of the AI user interface placing the tab buttons and panels separately in custom containers. .then( editor => { const tabsPlugin = editor.plugins.get( 'AITabs' ); for ( const id of tabsPlugin.view.getTabIds() ) { const tab = tabsPlugin.view.getTab( id ); // Display tab button and panel in a custom container. myButtonsContainer.appendChild( tab.button.element ); myPanelContainer.appendChild( tab.panel.element ); } } ) .catch( /* ... */ ); ``` #### Toggling the UI The user interface can be easily toggled by the users using the `'toggleAi'` toolbar button. The button becomes available for configuration when the [AIEditorIntegration](../../api/module%5Fai%5Faieditorintegration%5Faieditorintegration-AIEditorIntegration.html) plugin is enabled. The following example shows how to enable the `'toggleAi'` button in the main editor toolbar: ``` import { ClassicEditor } from 'ckeditor5'; import { /* ... */, AIEditorIntegration } from 'ckeditor5-premium-features'; ClassicEditor .create( { licenseKey: 'code |
| [Subscript](../api/module%5Fbasic-styles%5Fsubscript-Subscript.html) | 'subscript' | 'subscript' | subscript |
| [Superscript](../api/module%5Fbasic-styles%5Fsuperscript-Superscript.html) | 'superscript' | 'superscript' | superscript |
#### Supported input
By default, each feature can upcast more than one type of content. Here is the full list of elements supported by each feature, either when pasting from the clipboard, loading data on start, or using the [data API](../api/module%5Fcore%5Feditor%5Feditor-Editor.html#function-setData).
| Style feature | Supported input elements |
| -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| [Bold](../api/module%5Fbasic-styles%5Fbold-Bold.html) | , , <\* style="font-weight: bold"> (or numeric values that are greater or equal 600) |
| [Italic](../api/module%5Fbasic-styles%5Fitalic-Italic.html) | , , <\* style="font-style: italic"> |
| [Underline](../api/module%5Fbasic-styles%5Funderline-Underline.html) | , <\* style="text-decoration: underline"> |
| [Strikethrough](../api/module%5Fbasic-styles%5Fstrikethrough-Strikethrough.html) | , <\* style="word-wrap: break-word"> |
| [Subscript](../api/module%5Fbasic-styles%5Fsubscript-Subscript.html) | , <\* style="vertical-align: sub"> |
| [Superscript](../api/module%5Fbasic-styles%5Fsuperscript-Superscript.html) | , <\* style="vertical-align: super"> |
### Typing around inline code
CKEditor 5 allows for typing both at the inner and outer boundaries of code to make editing easier for the users.
**To type inside a code element**, move the caret to its (start or end) boundary. As long as the code remains highlighted (by default: less transparent gray), typing and applying formatting will be done within its boundaries:
**To type before or after a code element**, move the caret to its boundary, then press the Arrow key (→ or ←) once. The code is no longer highlighted and whatever text you type or formatting you apply will not be enclosed by the code element:
### Installation
After [installing the editor](#ckeditor5/latest/getting-started/installation/cloud/quick-start.html), add the plugins which you need to your plugin list. Then, simply configure the toolbar items to make the features available in the user interface.
### Related features
Check out also these CKEditor 5 features to gain better control over your content style and format:
* [Font styles](#ckeditor5/latest/features/font.html) – Easily and efficiently control the font [family](#ckeditor5/latest/features/font.html--configuring-the-font-family-feature), [size](#ckeditor5/latest/features/font.html--configuring-the-font-size-feature), [text or background color](#ckeditor5/latest/features/font.html--configuring-the-font-color-and-font-background-color-features).
* [Styles](#ckeditor5/latest/features/style.html) – Apply pre-configured styles to existing elements in the editor content.
* [Text alignment](#ckeditor5/latest/features/text-alignment.html) – Because it does matter whether the content is left, right, centered, or justified.
* [Case change](#ckeditor5/latest/features/case-change.html) – Turn a text fragment or block into uppercase, lowercase, or title case.
* [Code blocks](#ckeditor5/latest/features/code-blocks.html) – Insert longer, multiline code listings, expanding the inline code style greatly.
* [Highlight](#ckeditor5/latest/features/highlight.html) – Mark important words and passages, aiding a review or drawing attention to specific parts of the content.
* [Format painter](#ckeditor5/latest/features/format-painter.html) – Easily copy text formatting and apply it in a different place in the edited document.
* [Autoformatting](#ckeditor5/latest/features/autoformat.html) – Format the text on the go with Markdown code.
* [Remove format](#ckeditor5/latest/features/remove-format.html) – Easily clean basic text formatting.
### Common API
Each style feature registers a [command](#ckeditor5/latest/features/basic-styles.html--available-text-styles) which can be executed from code. For example, the following snippet will apply the bold style to the current selection in the editor:
```
editor.execute( 'bold' );
```
### Contribute
The source code of the feature is available on GitHub at .
source file: "ckeditor5/latest/features/block-quote.html"
## Block quote
The block quote feature lets you easily include block quotations or pull quotes in your content. It is also an attractive way to draw the readers’ attention to selected parts of the text.
### Demo
Use the block quote toolbar button in the editor below to see the feature in action. You can also type `>` followed by a space before the quotation to format it on the go thanks to the [autoformatting](#ckeditor5/latest/features/autoformat.html) feature.
This demo presents a limited set of features. Visit the [feature-rich editor example](#ckeditor5/latest/examples/builds-custom/full-featured-editor.html) to see more in action.
### Nested block quotes
Starting from version 27.1.0, CKEditor 5 will properly display a block quote nested in another block quote. This sort of structure is indispensable in email editors or discussion forums. The ability to cite previous messages and preserve a correct quotation structure is often crucial to maintain the flow of communication. Nested block quotes may also prove useful for scientific or academic papers, but articles citing sources and referring to previous writing would often use it, too.
Support for nested block quotes is provided as backward compatibility for loading pre-existing content, for example created in CKEditor 4\. Additionally, pasting content with nested block quotes is supported. You can also nest a block quote in another block quote using the [drag and drop](#ckeditor5/latest/features/drag-drop.html) mechanism – just select an existing block quote and drag it into another.
### Installation
After [installing the editor](#ckeditor5/latest/getting-started/installation/cloud/quick-start.html), add the feature to your plugin list and toolbar configuration:
### Configuration
#### Disallow nesting block quotes
By default, the editor supports inserting a block quote into another block quote.
To disallow nesting block quotes, you need to register an additional schema rule. It needs to be added before the data is loaded into the editor, hence it is best to implement it as a plugin:
```
function DisallowNestingBlockQuotes( editor ) {
editor.model.schema.addChildCheck( ( context, childDefinition ) => {
if ( context.endsWith( 'blockQuote' ) && childDefinition.name == 'blockQuote' ) {
return false;
}
} );
}
// Pass it via config.extraPlugins or config.plugins:
ClassicEditor
.create( {
extraPlugins: [ DisallowNestingBlockQuotes ],
// The rest of the configuration.
// ...
} )
.then( /* ... */ )
.catch( /* ... */ );
```
### Related features
Here are some other CKEditor 5 features that you can use similarly to the block quote plugin to structure your text better:
* [Block indentation](#ckeditor5/latest/features/indent.html) – Set indentation for text blocks such as paragraphs or lists.
* [Code block](#ckeditor5/latest/features/code-blocks.html) – Insert longer, multiline code listings.
* [Text alignment](#ckeditor5/latest/features/text-alignment.html) – Align your content left, right, center it, or justify.
* [Autoformatting](#ckeditor5/latest/features/autoformat.html) – Add formatting elements (such as block quotes) as you type with Markdown code.
### Common API
The [BlockQuote](../api/module%5Fblock-quote%5Fblockquote-BlockQuote.html) plugin registers:
* the `'blockQuote'` UI button component implemented by the [block quote UI feature](../api/module%5Fblock-quote%5Fblockquoteui-BlockQuoteUI.html),
* the `'blockQuote'` command implemented by the [block quote editing feature](../api/module%5Fblock-quote%5Fblockquoteediting-BlockQuoteEditing.html).
You can execute the command using the [editor.execute()](../api/module%5Fcore%5Feditor%5Feditor-Editor.html#function-execute) method:
```
// Applies block quote to the selected content.
editor.execute( 'blockQuote' );
```
### Contribute
The source code of the feature is available on GitHub at .
source file: "ckeditor5/latest/features/bookmarks.html"
## Bookmarks
The bookmarks feature allows for adding and managing the bookmarks anchors attached to the content of the editor. These provide fast access to important content sections, speed up the editing navigation and contribute to a more efficient content creation.
### Demo
Use the bookmark toolbar button in the editor below to see the feature in action. Or use the “Insert” command from the menu bar to add a bookmark. Add a unique name to identify the bookmark (for example, `Rights`). You can change the bookmark’s name or remove it by clicking the bookmark icon inside the content.
To use the bookmark as an anchor in the content, add a link and put the bookmark name as target. In the example below it could be `#Rights`. The link insertion panel will display all bookmarks available in the edited content (see the [Integration with the link feature](#ckeditor5/latest/features/bookmarks.html--integration-with-the-link-feature) section below).
This demo presents a limited set of features. Visit the [feature-rich editor example](#ckeditor5/latest/examples/builds-custom/full-featured-editor.html) to see more in action.
### Handling the anchor markup
Do not worry about setting a bookmark inside an empty paragraph. The block with the `a` tag will not be rendered in the final content (for example for printing).
The feature converts anchors into bookmarks during the [initialization of the editor](#ckeditor5/latest/getting-started/setup/getting-and-setting-data.html--initializing-the-editor-with-data) or while [replacing the editor data with setData()](#ckeditor5/latest/getting-started/setup/getting-and-setting-data.html--replacing-the-editor-data-with-setdata). The notation based on the `id` attribute in an `a` HTML element without a `href` attribute is converted. Similar notations meet the conditions, too:
* an `a` HTML element with a `name` attribute,
* an `a` HTML element with the same `name` and `id` attributes,
* an `a` HTML element with different `name` and `id` attributes.
By default, all bookmarks created in the editor only have the `id="..."` attribute in the [editor data](#ckeditor5/latest/getting-started/setup/getting-and-setting-data.html--getting-the-editor-data-with-getdata).
### Integration with the link feature
Bookmarks integrate with [links](#ckeditor5/latest/features/link.html), providing a smooth linking experience. If you have any bookmarks in your content, you can access the “Bookmarks” panel available during link creation. It will display all bookmarks available in the edited content. Choose one of the anchors from the list and use it as a link target.
### Installation
After [installing the editor](#ckeditor5/latest/getting-started/installation/cloud/quick-start.html), add the feature to your plugin list and toolbar configuration:
### Configuration
By default, the conversion of wrapped anchors is turned on. It allows to convert non-empty anchor elements into bookmarks. For example:
```
Foo bar baz
```
will be converted into a bookmark and the output will look like on the example below:
```
Foo bar baz
```
You can disable the automatic conversion by setting the [config.bookmark.enableNonEmptyAnchorConversion](../api/module%5Fbookmark%5Fbookmarkconfig-BookmarkConfig.html#member-enableNonEmptyAnchorConversion) to `false` in the editor configuration.
```
ClassicEditor
.create( {
// ... Other configuration options ...
bookmark: {
enableNonEmptyAnchorConversion: false
}
} )
.then( /* ... */ )
.catch( /* ... */ );
```
### Bookmark toolbar configuration
The bookmark UI contains a contextual toolbar that appears when a bookmark is selected. You can configure what items appear in this toolbar using the [config.bookmark.toolbar](../api/module%5Fbookmark%5Fbookmarkconfig-BookmarkConfig.html#member-toolbar) option.
The following toolbar items are available:
* `'bookmarkPreview'` – Shows the name of the bookmark.
* `'editBookmark'` – Opens a form to edit the bookmark name.
* `'removeBookmark'` – Removes the bookmark.
By default, the bookmark toolbar is configured as follows:
```
ClassicEditor
.create( {
bookmark: {
toolbar: [ 'bookmarkPreview', '|', 'editBookmark', 'removeBookmark' ]
}
} )
.then( /* ... */ )
.catch( /* ... */ );
```
#### Custom toolbar items
You can extend the bookmark toolbar with custom items by registering them in the [component factory](../api/module%5Fui%5Fcomponentfactory-ComponentFactory.html) and adding them to the toolbar configuration.
Here is an example of registering a custom component:
```
class MyCustomPlugin extends Plugin {
init() {
const editor = this.editor;
editor.ui.componentFactory.add( 'myCustomBookmarkInfo', locale => {
const button = new ButtonView( locale );
const bookmarkCommand = editor.commands.get( 'insertBookmark' );
button.bind( 'isEnabled' ).to( bookmarkCommand, 'value', href => !!href );
button.bind( 'label' ).to( bookmarkCommand, 'value' );
button.on( 'execute', () => {
// Add your custom component logic here
} );
return button;
} );
}
}
```
Once registered, the component can be used in the toolbar configuration:
```
ClassicEditor
.create( {
plugins: [ MyCustomPlugin, /* ... */ ],
bookmark: {
toolbar: [ 'myCustomBookmarkInfo', '|', 'editBookmark', 'removeBookmark' ]
}
} )
.then( /* ... */ )
.catch( /* ... */ );
```
### Bookmarks on blocks
At this time, if a bookmark is attached to a block, it appears before it. However, we plan to expand this solution in the future. We invite you to help us [gather feedback for linking directly to blocks and auto generating IDs](https://github.com/ckeditor/ckeditor5/issues/17264).
### Related features
Here are some other CKEditor 5 features that you can use similarly to the bookmark plugin to cross-link and structure your text better:
* The [link feature](#ckeditor5/latest/features/link.html) allows adding local and global URLs to the content.
* The [document outline](#ckeditor5/latest/features/document-outline.html) displays the list of sections (headings) of the document next to the editor.
* The [table of contents](#ckeditor5/latest/features/document-outline.html) lets you insert a widget with a list of headings (section titles) that reflects the structure of the document.
### Common API
The [Bookmark](../api/module%5Fbookmark%5Fbookmark-Bookmark.html) plugin registers the `'bookmark'` UI button component implemented by the [bookmark UI feature](../api/module%5Fbookmark%5Fbookmarkui-BookmarkUI.html), and the following commands:
* the `'insertBookmark'` command implemented by the [editing feature](../api/module%5Fbookmark%5Finsertbookmarkcommand-InsertBookmarkCommand.html).
* the `'updateBookmark'` command implemented by the [editing feature](../api/module%5Fbookmark%5Fupdatebookmarkcommand-UpdateBookmarkCommand.html).
### Contribute
The source code of the feature is available on GitHub at .
source file: "ckeditor5/latest/features/case-change.html"
## Case change
The case change feature lets you quickly change the letter case of the selected content. You can use it to format part of the text like a title or change it to all-caps and back.
### Demo
The demo below lets you test the case change. Place the cursor inside a block such as a paragraph, heading, or list item to affect the entire block. You can also select a text fragment you want to change. Then, apply the case formatting with the toolbar dropdown.
You can also use the Shift+F3 keyboard shortcut to cycle through case formats: UPPERCASE > lowercase > Title Case. Undo changes with Ctrl/Cmd+Z.
This demo presents a limited set of features. Visit the [feature-rich editor example](#ckeditor5/latest/examples/builds-custom/full-featured-editor.html) to see more in action.
### Installation
After [installing the editor](#ckeditor5/latest/getting-started/installation/cloud/quick-start.html), add the feature to your plugin list and toolbar configuration:
#### Activating the feature
To use this premium feature, you need to activate it with proper credentials. Refer to the [License key and activation](#ckeditor5/latest/getting-started/licensing/license-key-and-activation.html) guide for details.
### Configuring the title case mode
Approaches to the title case change vary. This is why we did not add a default ruleset. You can use the [config.caseChange.titleCase](../api/module%5Fcase-change%5Fcasechange-CaseChangeTitleCaseConfig.html) configuration to apply your rules. The configuration allows for adding exclusions – words that the feature should not capitalize, as shown in the example below. It also provides an entry point for writing custom sentence end detection mechanisms to handle exclusions in special positions in the sentence.
```
ClassicEditor
.create( {
// ... Other configuration options ...
caseChange: {
titleCase: {
excludeWords: [ 'a', 'an', 'and', 'as', 'at', 'but', 'by', 'en', 'for', 'if', 'in',
'nor', 'of', 'on', 'or', 'per', 'the', 'to', 'vs', 'vs.', 'via' ]
}
},
} )
.then( /* ... */ )
.catch( /* ... */ );
```
### Related features
Here are some more CKEditor 5 features that can help you format your content:
* [Basic text styles](#ckeditor5/latest/features/basic-styles.html) – The essentials, like **bold**, _italic_ and others.
* [Font styles](#ckeditor5/latest/features/font.html) – Control the font [family](#ckeditor5/latest/features/font.html--configuring-the-font-family-feature), [size](#ckeditor5/latest/features/font.html--configuring-the-font-size-feature), and [text or background color](#ckeditor5/latest/features/font.html--configuring-the-font-color-and-font-background-color-features).
* [Autoformatting](#ckeditor5/latest/features/autoformat.html) – Format the text on the go using Markdown.
* [Remove format](#ckeditor5/latest/features/remove-format.html) – Easily clean basic text formatting.
### Common API
The [CaseChange](../api/module%5Fcase-change%5Fcasechange-CaseChange.html) plugin registers:
* The `'caseChange'` UI dropdown component.
* The `'changeCaseUpper'`, `'changeCaseLower'`, and `'changeCaseTitle'` commands implemented by [CaseChangeCommand](../api/module%5Fcase-change%5Fcasechangecommand-CaseChangeCommand.html).
You can execute the command using the [editor.execute()](../api/module%5Fcore%5Feditor%5Feditor-Editor.html#function-execute) method:
```
// Change the case of selected content to uppercase.
editor.execute( 'changeCaseUpper' );
// Change the case of selected content to lowercase.
editor.execute( 'changeCaseLower' );
// Change the case of selected content to title case.
editor.execute( 'changeCaseTitle' );
```
source file: "ckeditor5/latest/features/cloud-services/cloud-services-overview.html"
## Cloud Services Overview
The CKEditor Cloud Services is a cloud platform that provides editing and real-time collaboration services. These services for managing comments, storing document revisions, handling the users, synchronizing the data, utilizing, configuring and managing features, importing and exporting documents and managing assets.
The platform primarily focuses on providing a backend for the CKEditor 5 features (via dedicated plugins), although some features can also be used directly through [RESTful APIs and hooks](#ckeditor5/latest/features/cloud-services/cloud-services-rest-api.html).
### Real-time collaboration
CKEditor Cloud Services offers a fast and highly scalable service for real-time collaboration, compatible with rich text editors built on top of the CKEditor 5 Framework. It is capable of handling real-time collaboration on text documents and tracking users connected to the document. It also serves as a storage for comments, suggestions, and revisions added to the document.
Apart from having a supporting backend to transmit operations, resolve conflicts, and apply changes between users connected to the same document, some features are needed on the client side to offer a full real-time collaboration experience:
* Showing multiple cursors and selections coming from other users.
* Showing users connecting to and disconnecting from the document.
* Offering the UI for managing comments and markers in the document.
The CKEditor Ecosystem offers a collection of plugins that can be integrated with any CKEditor 5 build to provide a fully flexible and customizable experience.
#### CKEditor 5 Real-time collaboration features
[CKEditor 5 Real-time collaboration features](#ckeditor5/latest/features/collaboration/real-time-collaboration/real-time-collaboration.html) let you customize any CKEditor 5 integration to include real-time collaborative editing, commenting, and track changes features and tailor them to your needs.
Real-time collaboration consists of four features delivered as separate plugins that can be used with any CKEditor 5 build:
* [Real-time collaborative editing](#ckeditor5/latest/features/collaboration/real-time-collaboration/real-time-collaboration.html) – Allows for editing the same document by multiple users simultaneously. It also automatically solves all conflicts if users make changes at the same time.
* [Real-time collaborative comments](#ckeditor5/latest/features/collaboration/comments/comments.html) – Makes it possible to add comments to any part of the content in the editor.
* [Real-time collaborative track changes](#ckeditor5/latest/features/collaboration/track-changes/track-changes.html) – Changes to the content are saved as suggestions that can be accepted or discarded later.
* [Real-time collaborative revision history](#ckeditor5/latest/features/collaboration/revision-history/revision-history.html) – Multiple versions of the document are available, an older version can be restored.
* [Users selection and presence list](#ckeditor5/latest/features/collaboration/users.html) – Shows the selection of other users and lets you view the list of users currently editing the content in the editor.
All of the above features are customizable. This makes implementing real-time collaborative editing within your application a highly customizable out-of-the-box experience.
For an introduction to CKEditor 5 Collaboration Features refer to the [Collaboration overview](#ckeditor5/latest/features/collaboration/real-time-collaboration/real-time-collaboration.html).
### Export features
CKEditor Cloud Services offers a fast and highly scalable service enabling the user to export documents either to a Microsoft Word document or to a PDF document. Both of these are available as a service, making it possible to feed the data straight into the Cloud Services server for more advanced use or as convenient WYSIWYG editor plugins for ease of use in less demanding cases.
#### Export to PDF
The Export to PDF converter provides an API for converting HTML documents to PDF files. The service generates a file and returns it to the user so they can save it in the `.pdf` format on their disk. This allows you to easily turn your content into the portable final PDF format file collection. Available both [as a service endpoint](#cs/latest/guides/export-to-pdf/overview.html) (a premium feature) and [as a plugin](#ckeditor5/latest/features/converters/export-pdf.html) (needs to be added to the editor separately).
#### Export to Word
The Export to Word converter provides an API for converting HTML documents to Microsoft Word `.docx` files. The service generates a Word file and returns it to the user so they can save it in the `.docx` format on their disk. This allows you to easily export your content to the Microsoft Word format. Available both [as a service endpoint](#cs/latest/guides/export-to-word/overview.html) (a premium feature) and [as a plugin](#ckeditor5/latest/features/converters/export-word.html) (needs to be added to the editor separately).
### Import from Word
The Import from Word converter is a fast and highly scalable service enabling the user to import documents from a Microsoft Word `.docx` file. The feature is available as a service, making it possible to send a `.docx` file [straight into the Cloud Services server](#cs/latest/guides/import-from-word/overview.html) for more advanced use or as a convenient [CKEditor 5 WYSIWYG editor plugin](#ckeditor5/latest/features/converters/import-word/import-word.html) for the ease of use in less demanding cases.
The DOCX to HTML converter provides an API for converting Microsoft Word `.docx` files to HTML documents. The service generates HTML data and returns it to the user so they can save it in the HTML format on their disk.
### CKBox
[CKBox](#ckbox/latest/guides/index.html) is a service that manages document assets and images. It allows for seamless uploading and management of assets within a document. The service stores them in persistent storage and provides tools to optimize image size and manage attributes, such as alternative text. Once an asset is stored, it can be reused in multiple documents.
If you plan to use it withing CKEditor 5, refer to the [CKBox plugin documentation](#ckeditor5/latest/features/file-management/ckbox.html) for details.
### Cloud region and on-premises options
Cloud Services are available both as a cloud solution (SaaS) and as a self-hosted (on-premises) version. You can learn more about the different approaches in the dedicated [SaaS vs. On-premises](#cs/latest/guides/saas-vs-on-premises.html) guide in the Cloud Services documentation.
The cloud solution offers two regions to host your services. You can choose the [EU, US, or both cloud regions](#cs/latest/guides/saas-vs-on-premises.html--cloud-region) when working with a cloud-hosted SaaS setup.
### Next steps
* If you already use collaboration features without Real-time Collaboration you can refer to our dedicated guide about [migration of data between asynchronous and RTC editors](#cs/latest/guides/collaboration/migrating-to-rtc.html).
* If you need to save your documents in portable file formats, check out the [Export to PDF](#ckeditor5/latest/features/converters/export-pdf.html) or [Export to Word](#ckeditor5/latest/features/converters/export-word.html) feature guides.
* If you need to import your documents from the `.docx` format, learn more about the [Import from Word](#ckeditor5/latest/features/converters/import-word/import-word.html) feature.
* If you are interested in the CKBox asset manager, check the [CKBox quick start](#ckbox/latest/guides/quick-start.html) guide for a short instruction on how to start using CKBox.
source file: "ckeditor5/latest/features/cloud-services/cloud-services-rest-api.html"
## REST API and Webhooks
### Cloud Services RESTful APIs
CKEditor Cloud Services offer several REST APIs that can be used for server integration. They provide a lot of powerful methods that make it possible to control and manage data. They can be used to control content revisions, handle users or manage document conversions.
The APIs currently include:
* **CKEditor Cloud Services RESTful APIs** – Provides a full-featured RESTful API that you can use to create a server-to-server integration. The API documentation is available at . It is an aggregator of all RESTful APIs currently available.
* **CKBox RESTful API** – Provides an API for managing data stored in the CKBox. The API documentation is available at .
* **HTML to PDF Converter API** – Provides an API for converting HTML/CSS documents to PDF format. The API documentation is available at .
* **HTML to DOCX Converter API** – Provides an API for converting HTML documents to Microsoft Word `.docx` files. The API documentation is available at
* **DOCX to HTML Converter API** – Provides an API for converting Microsoft Word `.docx`/`.dotx` files to HTML documents. The API documentation is available at .
#### Usage
Each method can be used for different purposes. For example, the REST API methods for comments allow for synchronizing comments between CKEditor Cloud Services and another system. In addition to that, CKEditor Cloud Services can be used as a database for comments because it is possible to download them via the REST API at the time they are being displayed.
An example of using another API method is getting the content of the document from a collaborative editing session. This feature can be used to build an autosave mechanism for the document, which should reduce transfer costs – autosave requests are not executed by each connected user but only by the system once at a time.
### Webhooks
Webhooks resemble a notification mechanism that can be used to build integrations with CKEditor Cloud Services. CKEditor Cloud Services sends an HTTP POST request to a configured URL when specified events are triggered.
Webhooks can be used for data synchronization between CKEditor Cloud Services and another system or to build a notifications system. For example, thanks to webhooks, the system might notify the users via email about changes made in the document.
To learn more about CKEditor Environment webhooks, refer to the [Webhooks guide](#cs/latest/developer-resources/webhooks/overview.html) in the Cloud Services documentation.
source file: "ckeditor5/latest/features/cloud-services/server-side-editor-api.html"
## Server-side editor API
Server-side Editor API enables deep and complex integration of your application with all document data, enabling you to manipulate content and manage collaborative data such as suggestions, comments, and revision history, and much more, directly from your server-side code (without running editor instance on the client).
The server-side editor REST API endpoint allows you to execute any JavaScript code that uses the CKEditor 5 API, that could be executed by a browser, but without a need to open the editor by a human user. Instead, the script is executed on the Cloud Services server. Please note that there are some [security-related limitations](#cs/latest/developer-resources/server-side-editor-api/security.html) for the executed JavaScript code.
### Why use server-side editor API?
While CKEditor 5 provides a rich client-side editing experience, there are many scenarios where server-side content processing is essential:
* **Automation**: Run content processing tasks as part of your backend workflows.
* **Scalability**: Process multiple documents simultaneously without client-side limitations.
* **Security**: Process sensitive content in a controlled environment without exposing it to client-side manipulation.
* **Performance**: Handle large-scale content operations without impacting the user’s browser.
* **Consistency**: Ensure uniform content changes across multiple documents.
* **Integration**: Connect with other server-side systems and databases directly.
### Common use cases
* **Deep integration**: Build custom features that can manage document content and related document data straight from your application UI, without a need to open the editor.
* **Content migration**: Restructure and update references across multiple documents, perfect for website redesigns or content reorganization.
* **Shared content blocks**: Automatically update reusable content (like headers, footers, or common sections) across all documents that use it.
* **Automated review systems**: Build systems that automatically review and suggest content changes, like grammar checks or style improvements.
* **AI-powered editing**: Make automated suggestions while users are actively editing, helping improve content quality.
* **Automated publishing**: Prepare and process content for publication, including formatting, metadata updates, and resolving comments.
### API examples
Below, you will find several examples of practical server-side API applications. There are far more possibilities available.
#### Getting started with server-side editor API
This guide explains how to write scripts that can be executed through the Server-side Editor API endpoint. The following sections provide examples of such scripts, each demonstrating a specific use case that can be automated on the server side.
For information about setting up and using the endpoint itself, see the complementary [Cloud Services Server-side Editor API](#cs/latest/developer-resources/server-side-editor-api/editor-scripts.html) documentation.
#### Working with content
##### Getting editor data
The most basic action you can perform is getting the editor data.
```
// Get the editor data.
const data = editor.getData();
// The endpoint will respond with the returned data.
return data;
```
You can also retrieve the data with specific options and include additional information about the document:
```
// Get the editor data with suggestion highlights visible.
const data = editor.getData( { showSuggestionHighlights: true } );
// Get additional document information.
const wordCount = editor.plugins.get( 'WordCount' ).getWords();
// Return both the content and metadata.
return {
content: data,
wordCount: wordCount
};
```
This approach allows you to not only retrieve the document content but also process it, extract metadata, or prepare it for specific use cases like exports or integrations with other systems.
##### Using commands
Commands provide a high-level API to interact with the editor and change the document content. Most editor features provide a command that you can use to trigger some action on the editor.
Here is a simple example. Imagine you need to fix a typo in a company name that is spread across multiple documents. Instead of forcing the user to do it manually, you can do it with a single line of code:
```
// Replace all instances of "Cksource" with "CKSource" in the document.
editor.execute( 'replaceAll', 'CKSource', 'Cksource' );
```
This command will find all instances of “Cksource” in your documents and change them to “CKSource”. This is perfect for making bulk updates in multiple documents. Simply, execute this call for every document you would like to change.
Most CKEditor 5 features expose one or multiple commands that can be used to manipulate the editor’s state. To learn what commands are available, visit [Features guides](#ckeditor5/latest/features/index.html), and look for the “Common API” section at the end of each guide, where commands related to that feature are described.
##### Insert HTML content
When you have HTML content ready (for example, from another system or a template), you can insert it directly into the editor. This is often simpler than building the content piece by piece using the editor API.
```
// The HTML content we want to add.
const html = 'New section
This is a new section inserted into the document using server-side editor API.
';
// Convert HTML to the editor's model.
const model = editor.data.parse( html );
// Get the root element and create an insertion position.
const root = editor.model.document.getRoot();
const insertPosition = editor.model.createPositionAt( root, 1 );
// Insert the content at the specified position.
editor.model.insertContent( model, insertPosition );
```
##### Using editor model API
If you cannot find a command that would perform a specific action on the document, you can use the editor API to apply precise changes. This approach offers the greatest flexibility and should cover any needs you may have. It requires, however, a better understanding of CKEditor internals.
For example, consider a scenario where you need to update all links in your document from `/docs/` to `/documents/`. This is a common task when moving content between environments or updating your site structure.
```
// Get the root element and create a range that covers all content.
const root = editor.model.document.getRoot();
const range = editor.model.createRangeIn( root );
const items = Array.from( range.getItems() );
editor.model.change( writer => {
for ( const item of items ) {
let href = item.getAttribute( 'linkHref' );
if ( item.is( 'textProxy' ) && href ) {
// Update the link URL.
href = href.replace( '/docs/', '/documents/' );
writer.setAttribute( 'linkHref', href, item );
}
}
} );
```
This approach is particularly useful when you have to modify the document data in some specific way, and the generic, high-level API cannot cover it.
To learn more about working with the editor engine, see the [Editing engine](#ckeditor5/latest/framework/architecture/editing-engine.html) guide.
#### Working with comments
The [comments](#ckeditor5/latest/features/collaboration/comments/comments.html) feature allows your users to have discussions attached to certain parts of your documents. You can use the comments feature API to implement interactions with comments with no need to open the editor itself.
##### Creating comments
You can create new comments using the `addCommentThread` command. By default, this command would create a comment thread on the current selection and create a “draft” comment thread, which might not be what you want in a server-side context. However, you can customize it using two parameters: `ranges` to specify where to place the comment, and `comment` to set its initial content.
Here is an example that shows how to automatically add comments to images that are missing the `alt` attribute:
```
const model = editor.model;
// Create a range on the whole content.
const range = model.createRangeIn( model.document.getRoot() );
editor.model.change( () => {
// Go through each item in the editor content.
for ( const item of range.getItems() ) {
const isImage = item.is( 'element', 'imageBlock' ) || item.is( 'element', 'imageInline' );
// Find images without `alt` attribute
if ( isImage && !item.getAttribute( 'alt' ) ) {
const commentRange = model.createRangeOn( item );
const firstCommentMessage = 'The alt attribute is missing.';
// Add a comment on the image.
editor.execute(
'addCommentThread',
{
ranges: [ commentRange ],
comment: firstCommentMessage
}
);
}
}
} );
```
The above example shows how to automatically review your content and add comments where needed. You could use similar code to build automated content review systems, accessibility checkers, or any other validation workflows.
##### Resolving comments
You can use the comments feature API to manage existing comments in your documents. For example, here is a way to resolve all comment threads in a given document:
```
// Get all comment threads from the document.
const threads = editor.plugins.get( 'CommentsRepository' ).getCommentThreads();
// Resolve all open comment threads.
for ( const thread of threads ) {
if ( !thread.isResolved ) {
thread.resolve();
}
}
```
This code is particularly useful when you need to clean up a document. You might use it to automatically resolve old discussions, prepare documents for publication, or maintain a clean comment history in your content management system.
#### Working with track changes
You can leverage the [track changes](#ckeditor5/latest/features/collaboration/track-changes/track-changes.html) feature API to manage existing content suggestions, retrieve final document data with all suggestions accepted, or implement automated or AI-powered content reviews.
##### Working with suggestions
You can use the [track changes data plugin](../../api/module%5Ftrack-changes%5Ftrackchangesdata-TrackChangesData.html) to get the document data with all suggestions either accepted or discarded:
```
// Get the track changes data plugin.
const trackChangesData = editor.plugins.get( 'TrackChangesData' );
// Get the document data with all suggestions rejected.
// You can also use `trackChangesData.getDataWithAcceptedSuggestions()` to get data with all suggestions accepted.
const data = trackChangesData.getDataWithDiscardedSuggestions();
return data;
```
This is particularly useful when you need to show or process the “original” or the “final” document data.
While the previous example could be used to get the data, you may also want to permanently accept or discard suggestions. You can do this for all suggestions at once using the following command:
```
// Accept all suggestions in the document.
// Use `discardAllSuggestions` command to discard all suggestions instead.
editor.execute( 'acceptAllSuggestions' );
```
This command is especially helpful when finalizing documents or when working with applications where a document is split into multiple CKEditor document instances but is treated as one unit in the application. In such cases, you might, for example, want to offer a button to accept all suggestions across all document parts.
For more granular control, you can also manage individual suggestions:
```
// Get the track changes editing plugin.
const trackChangesEditing = editor.plugins.get( 'TrackChangesEditing' );
// Get a specific suggestion by its ID.
const suggestion = trackChangesEditing.getSuggestion( 'suggestion-id' );
// Accept the suggestion.
suggestion.accept();
// Or discard it.
// suggestion.discard();
```
It allows to display and manage suggestions outside the editor, for example in a separate application view where users can see all comments and suggestions and resolve them without going into the editor.
##### Creating new suggestions
Track changes is integrated with most editor commands. If you wish to change the document using commands and track these changes, all you need to do is turn on track changes mode.
Below is an example that shows a basic text replacement:
```
// Enable track changes to mark our edits as suggestions.
editor.execute( 'trackChanges' );
// Make a simple text replacement.
editor.execute( 'replaceAll', 'CKSource', 'Cksource' );
```
The `trackChanges` command ensures that all changes made by other commands are marked as suggestions.
Since Track changes feature is integrated with `Model#insertContent()` function, you can easily suggest adding some new content:
```
// Enable track changes for the new content.
editor.execute( 'trackChanges' );
// Prepare the new content to be added.
const modelFragment = editor.data.parse( 'Hello world!' );
// Add the content as a suggestion at the beginning of the document.
const firstElement = editor.model.document.getRoot().getChild( 0 );
const insertPosition = editor.model.createPositionAt( firstElement, 0 );
editor.model.insertContent( modelFragment, insertPosition );
```
Now, let’s see how to suggest deleting a specified part of the document. To do this, use `Model#deleteContent()` while in track changes mode:
```
// Enable track changes so that deleted content is marked,
// instead of being actually removed from the content.
editor.execute( 'trackChanges' );
// Get the section we want to mark as deletion suggestion.
const firstElement = editor.model.document.getRoot().getChild( 0 );
// `deleteContent()` expects selection-to-remove as its parameter.
const deleteRange = editor.model.createRangeIn( firstElement );
const deleteSelection = editor.model.createSelection( deleteRange );
// Track changes is integrated with `deleteContent()`, so the content
// will be marked as suggestion, instead of being removed from the document.
editor.model.deleteContent( deleteSelection );
```
You can use `insertContent()` and `deleteContent()` methods in the following scenarios:
* Automated suggestions based on external data.
* Creating templates that need review before finalization.
* Integrating with content management systems to propose changes.
* Building custom workflows for content creation and review.
##### Attribute modifications
If you wish to create attributes suggestions using the editor model API, you need to specifically tell the track changes features to record these changes. Let’s look at how to correctly make a suggestion to update links URLs:
```
// Get the track changes editing plugin for direct access to suggestion recording.
const trackChangesEditing = editor.plugins.get( 'TrackChangesEditing' );
// Get the root element and create a range that covers all content.
const root = editor.model.document.getRoot();
const range = editor.model.createRangeIn( root );
const items = Array.from( range.getItems() );
// Process each item in the document.
for ( const item of items ) {
editor.model.change( writer => {
// Use `recordAttributeChanges to ensure the change is properly recorded as a suggestion.
trackChangesEditing.recordAttributeChanges( () => {
let href = item.getAttribute( 'linkHref' );
// Only process text proxies (parts of text nodes) that have a `linkHref` attribute.
if ( item.is( 'textProxy' ) && href ) {
// Update the link URL, for example changing '/docs/' to '/documents/'.
href = href.replace( '/docs/', '/documents/' );
// Set the new attribute value, which will be recorded as a suggestion.
writer.setAttribute( 'linkHref', href, item );
}
} );
} );
}
```
##### Extracting additional suggestion data
Track changes feature stores and exposes more data than is saved on the Cloud Services servers. This dynamic data is evaluated by the feature on-the-fly, hence it is not stored. You can use the editor API to get access to that data.
All active suggestions have a related annotation (UI “balloon” element, located in the sidebar or displayed above the suggestion). You can, for example, retrieve a suggestion label that is displayed inside a suggestion balloon annotation.
Another useful information is content on which the suggestion was made (together with some additional context around it).
The following example demonstrates retrieving additional suggestion data:
```
const results = [];
const trackChangesUI = editor.plugins.get( 'TrackChangesUI' );
const annotations = editor.plugins.get( 'Annotations' ).collection;
// Go through all annotations available in the document.
for ( const annotation of annotations ) {
// Check if this is a suggestion annotation.
// Note, that another annotation type is `'comment'`.
// You can process comments annotations to retrieve additional comments data.
if ( annotation.type.startsWith( 'suggestion' ) ) {
const suggestion = trackChangesUI.getSuggestionForAnnotation( annotation );
// Get the suggestion label.
const label = annotation.innerView.description;
// Evaluate the content on which the suggestion was made.
// First, get all the ranges in the content related to this suggestion.
const ranges = [];
// Note, that suggestions can be organized into "chains" when they
// are next to each other. Get all suggestions adjacent to the processed one.
for ( const adjacentSuggestion of suggestion.getAllAdjacentSuggestions() ) {
ranges.push( ...adjacentSuggestion.getRanges() );
}
let contextHtml = '';
if ( ranges.length ) {
const firstRange = ranges[ 0 ];
const lastRange = ranges[ ranges.length - 1 ];
// Find the common ancestor for the whole suggestion context.
const commonAncestor = firstRange.start.getCommonAncestor( lastRange.end );
if ( commonAncestor ) {
// Stringify the entire common ancestor element as HTML, highlighting suggestions.
contextHtml = editor.data.stringify( commonAncestor, { showSuggestionHighlights: true } );
}
}
results.push( {
type: 'suggestion',
id: suggestion.id,
label,
context: contextHtml
} );
}
}
return results;
```
#### Working with revision history
Use the [revision history](#ckeditor5/latest/features/collaboration/revision-history/revision-history.html) feature API to build more functional integration between your application and the document revisions data.
##### Saving revisions
You can use Revision history API to save a new revision directly from your application backend:
```
// Save the current state as a new revision.
editor.plugins.get( 'RevisionTracker' ).saveRevision( { name: 'New revision' } );
```
This can be used on an unchanged document to simply create a document snapshot, or after you performed some changes to save them as a new revision.
Revision history API can help you build an automated mechanism that will automatically create revisions in some time intervals, or based on other factors. It can be particularly useful when you need to create checkpoints for your documents to maintain an audit trail of content modifications.
##### Working with revision data
In more complex scenarios, you might have a need to work with content coming from various revisions of your document:
```
// Get the revision management tools.
const revisionHistory = editor.plugins.get( 'RevisionHistory' );
const revisionTracker = editor.plugins.get( 'RevisionTracker' );
// Get the latest revision from history.
const revision = revisionHistory.getRevisions()[ 0 ];
// Get the document content and document roots attributes.
const documentData = await revisionTracker.getRevisionDocumentData( revision );
const attributes = await revisionTracker.getRevisionRootsAttributes( revision );
return { documentData, attributes };
```
This is useful if you need particular revision data for further processing. It will allow you build custom backend features based on revisions, like previewing revisions data outside of editor, exporting a particular revision to PDF, or integrating revisions data with external systems.
### Custom plugins
Server-side editor API capabilities could be extended by creating custom plugins. Custom plugins may implement complex logic and maintain reusable functionality across multiple server-side operations. Through the editor instance, you can access custom plugin API in your server-side scripts. This approach will make your code more organized and maintainable. Using a plugin will be necessary if you need to import a class or a function from one of the CKEditor 5 packages to implement your desired functionality.
To use custom plugins in server-side executed scripts, simply add them to the editor bundle that you upload to Cloud Services. Then you can access them through the editor instance:
```
// Get your custom plugin instance.
const myPlugin = editor.plugins.get( 'MyCustomPlugin' );
// Use the plugin's API.
return myPlugin.doSomething();
```
For more information about creating custom plugins, see the [Plugins architecture](#ckeditor5/latest/framework/architecture/plugins.html) guide and the [Creating a basic plugin](#ckeditor5/latest/framework/tutorials/creating-simple-plugin-timestamp.html) tutorial.
### Error handling
If an error occurs while processing the script on the server side, the API will return an error message and include the specific information about the encountered problem in the `data.error` object. Additionally, a `trace_id` is returned, which allows you to look up more detailed information about the specific event on the server. This makes it easier to quickly diagnose and resolve issues based on the provided identifier.
source file: "ckeditor5/latest/features/code-blocks.html"
## Code blocks
The code block feature lets you insert and edit blocks of pre-formatted code. It is perfect for presenting programming-related content in an attractive, easy-to-read form.
### Demo
Use the code block toolbar button and the type dropdown to insert a desired code block. Alternatively, start a line with a backtick, and the [autoformatting feature](#ckeditor5/latest/features/autoformat.html) will format it as a code block. To add a paragraph under a code block, simply press Enter three times.
This demo presents a limited set of features. Visit the [feature-rich editor example](#ckeditor5/latest/examples/builds-custom/full-featured-editor.html) to see more in action.
Each code block has a [specific programming language assigned](#ckeditor5/latest/features/code-blocks.html--configuring-code-block-languages) (like “Java” or “CSS”; this is configurable) and supports basic editing tools, for instance, [changing the line indentation](#ckeditor5/latest/features/code-blocks.html--changing-line-indentation) using the keyboard.
### Installation
After [installing the editor](#ckeditor5/latest/getting-started/installation/cloud/quick-start.html), add the feature to your plugin list and toolbar configuration:
### Configuring code block languages
Each code block can be assigned a programming language. The language of the code block is represented as a CSS class of the `` element, both when editing and in the editor data:
```
window.alert( 'Hello world!' )
```
It is possible to configure which languages are available to the users. You can use the [codeBlock.languages](../api/module%5Fcode-block%5Fcodeblockconfig-CodeBlockConfig.html#member-languages) configuration and define your own languages. For example, the following editor supports only two languages (CSS and HTML):
```
ClassicEditor
.create( {
// ... Other configuration options ...
codeBlock: {
languages: [
{ language: 'css', label: 'CSS' },
{ language: 'html', label: 'HTML' }
]
}
} )
.then( /* ... */ )
.catch( /* ... */ );
```
By default, the CSS class of the `` element in the data and editing is generated using the `language` property (prefixed with “language-”). You can customize it by specifying an optional `class` property. You can set **multiple classes** but **only the first one** will be used as defining language class:
```
ClassicEditor
.create( {
// ... Other configuration options ...
codeBlock: {
languages: [
// Do not render the CSS class for the plain text code blocks.
{ language: 'plaintext', label: 'Plain text', class: '' },
// Use the "php-code" class for PHP code blocks.
{ language: 'php', label: 'PHP', class: 'php-code' },
// Use the "js" class for JavaScript code blocks.
// Note that only the first ("js") class will determine the language of the block when loading data.
{ language: 'javascript', label: 'JavaScript', class: 'js javascript js-code' },
// Python code blocks will have the default "language-python" CSS class.
{ language: 'python', label: 'Python' }
]
}
} )
.then( /* ... */ )
.catch( /* ... */ );
```
#### Integration with code highlighters
Although live code block highlighting _**is impossible when editing**_ in CKEditor 5 ([learn more](https://github.com/ckeditor/ckeditor5/issues/436#issuecomment-548399675)), the content can be highlighted when displayed in the frontend (for example, in blog posts or website content).
The code language [configuration](../api/module%5Fcode-block%5Fcodeblockconfig-CodeBlockConfig.html#member-languages) helps to integrate with external code highlighters (for example, [highlight.js](https://highlightjs.org/) or [Prism](https://prismjs.com/)). Refer to the documentation of the highlighter of your choice and make sure the CSS classes configured in `codeBlock.languages` correspond with the code syntax autodetection feature of the highlighter.
### Tips and tweaks
#### Editing text around code blocks
There could be situations when there is no obvious way to set the caret before or after a block of code and type. This can happen when the code block is preceded or followed by a widget (like a table) or when the code block is the first or the last child of the document (or both).
* To type **before the code block**: Put the selection at the beginning of the first line of the code block and press Enter. Move the selection to the empty line that has been created and press Enter again. A new paragraph that you can type in will be created before the code block.
* To type **after the code block**: Put the selection at the end of the last line of the code block and press Enter three times. A new paragraph that you can type in will be created after the code block.
#### Changing line indentation
You can change the indentation of the code using keyboard shortcuts and toolbar buttons:
* To **increase** indentation: Select the line (or lines) you want to indent. Hit the Tab key or press the “Increase indent” button in the toolbar.
* To **decrease** indentation: Select the line (or lines) the indent should decrease. Hit the Shift+Tab keys or press the “Decrease indent” button in the toolbar.
#### Preserving line indentation
To speed up the editing, when typing in a code block, the indentation of the current line is preserved when you hit Enter and create a new line. If you want to change the indentation of the new line, take a look at [some easy ways to do that](#ckeditor5/latest/features/code-blocks.html--changing-line-indentation).
### Related features
Here are some similar CKEditor 5 features that you may find helpful:
* [Basic text styles](#ckeditor5/latest/features/basic-styles.html) – Use the `code` formatting for short inline code chunks.
* [Block quote](#ckeditor5/latest/features/block-quote.html) – Include block quotations or pull quotes in your rich-text content.
* [Block indentation](#ckeditor5/latest/features/indent.html) – Set indentation for text blocks such as paragraphs or lists.
* [Autoformatting](#ckeditor5/latest/features/autoformat.html) – Format the content on the go with Markdown code.
### Common API
The [CodeBlock](../api/module%5Fcode-block%5Fcodeblock-CodeBlock.html) plugin registers:
* The `'codeBlock'` split button with a dropdown allowing to choose the language of the block.
* The ['codeBlock'](../api/module%5Fcode-block%5Fcodeblockcommand-CodeBlockCommand.html) command.
The command converts selected WYSIWYG editor content into a code block. If no content is selected, it creates a new code block at the place of the selection.
You can choose which language the code block is written in when executing the command. The language will be set in the editor model and reflected as a CSS class visible in the editing view and the editor (data) output:
```
editor.execute( 'codeBlock', { language: 'css' } );
```
When executing the command, you can use languages defined by the [codeBlock.languages](../api/module%5Fcode-block%5Fcodeblockconfig-CodeBlockConfig.html#member-languages) configuration. The default list of languages is as follows:
```
codeBlock.languages: [
{ language: 'plaintext', label: 'Plain text' }, // The default language.
{ language: 'c', label: 'C' },
{ language: 'cs', label: 'C#' },
{ language: 'cpp', label: 'C++' },
{ language: 'css', label: 'CSS' },
{ language: 'diff', label: 'Diff' },
{ language: 'go', label: 'Go' },
{ language: 'html', label: 'HTML' },
{ language: 'java', label: 'Java' },
{ language: 'javascript', label: 'JavaScript' },
{ language: 'php', label: 'PHP' },
{ language: 'python', label: 'Python' },
{ language: 'ruby', label: 'Ruby' },
{ language: 'typescript', label: 'TypeScript' },
{ language: 'xml', label: 'XML' }
]
```
**Note**: If you execute the command with a specific `language` when the selection is anchored in a code block, and use the additional `forceValue: true` parameter, it will update the language of this particular block.
```
editor.execute( 'codeBlock', { language: 'java', forceValue: true } );
```
**Note**: If the selection is already in a code block, executing the command will convert the block back into plain paragraphs.
* The ['indentCodeBlock'](../api/module%5Fcode-block%5Findentcodeblockcommand-IndentCodeBlockCommand.html) and ['outdentCodeBlock'](../api/module%5Fcode-block%5Foutdentcodeblockcommand-OutdentCodeBlockCommand.html) commands.
Both commands are used by the Tab and Shift+Tab keystrokes as described in the [section about indentation](#ckeditor5/latest/features/code-blocks.html--changing-line-indentation):
* The `'indentCodeBlock'` command is enabled when the selection is anchored anywhere in the code block and it allows increasing the indentation of the lines of code. The indentation character (sequence) is configurable using the [codeBlock.indentSequence](../api/module%5Fcode-block%5Fcodeblockconfig-CodeBlockConfig.html#member-indentSequence) configuration.
* The `'outdentCodeBlock'` command is enabled when the indentation of any code lines within the selection can be decreased. Executing it will remove the indentation character (sequence) from these lines, as configured by [codeBlock.indentSequence](../api/module%5Fcode-block%5Fcodeblockconfig-CodeBlockConfig.html#member-indentSequence).
### Contribute
The source code of the feature is available on GitHub at .
source file: "ckeditor5/latest/features/collaboration/annotations/annotations-custom-configuration.html"
## Configuration of annotations
Tweaking the configuration of CKEditor 5 collaboration features is another easy way to change the behavior of the collaboration features views.
### Collaboration features configuration
The full documentation of available configuration options can be found in the [comments feature editor configuration](../../../api/module%5Fcore%5Feditor%5Feditorconfig-EditorConfig.html#member-comments), the [track changes feature editor configuration](../../../api/module%5Fcore%5Feditor%5Feditorconfig-EditorConfig.html#member-trackChanges) and the [sidebar feature editor configuration](../../../api/module%5Fcore%5Feditor%5Feditorconfig-EditorConfig.html#member-sidebar) guides.
Note that comments configuration also applies to comments in a suggestion thread.
### Comment editor configuration
The editor used for adding and editing comments is also a CKEditor 5 instance. By default, it uses the following plugins:
* [Essentials](../../../api/module%5Fessentials%5Fessentials-Essentials.html),
* [Paragraph](../../../api/module%5Fparagraph%5Fparagraph-Paragraph.html),
These plugins allow for creating the comment content with some basic styles.
However, it is possible to extend the comment editor configuration and add some extra plugins or even overwrite the entire configuration and replace the list of plugins. You can modify the comment editor configuration by using the [comments.editorConfig](../../../api/module%5Fcomments%5Fconfig-CommentsConfig.html#member-editorConfig) property in the main editor configuration.
See the sample below to learn how to add the [Mention](../../../api/module%5Fmention%5Fmention-Mention.html) plugin to the comment editor:
Note that additional plugins need to be a part of the same package, just like the main editor plugins. By default, the `ckeditor5` package carries all open-source plugins, while the `ckeditor5-premium-plugins` one keeps all premium plugins at hand.
### Sidebar configuration
The sidebar configuration allows for setting the container element for the sidebar and tuning the positioning mechanism. Check the [sidebar configuration](../../../api/module%5Fcomments%5Fconfig-AnnotationsSidebarConfig.html) API documentation for all available options.
#### Example of use
```
// ...
const editorConfig = {
// ...
comments: {
// Show the first comment and the two most recent comments when collapsed.
maxCommentsWhenCollapsed: 3,
// Make comments shorter when collapsed.
maxCommentCharsWhenCollapsed: 100,
// Allow for up to 3 comments before collapsing.
maxThreadTotalWeight: 600,
// You may set the comments editor configuration.
// In this case, use the default configuration.
editorConfig: {}
},
sidebar: {
container: document.querySelector( '#sidebar' ),
preventScrollOutOfView: true
},
// ...
};
ClassicEditor.create( editorConfig );
```
Using the [comments.CommentThreadView](../../../api/module%5Fcomments%5Fconfig-CommentsConfig.html#member-CommentThreadView) and [comments.CommentView](../../../api/module%5Fcomments%5Fconfig-CommentsConfig.html#member-CommentView) configuration options is described in the [Annotations custom view](#ckeditor5/latest/features/collaboration/annotations/annotations-custom-view.html) guide.
### Custom date format
The comments feature allows you to set a custom date format for comments and suggestions. To enable that, pass a function to the [locale.dateTimeFormat](../../../api/module%5Fcollaboration-core%5Fconfig-LocaleConfig.html#member-dateTimeFormat) property in the main editor configuration. This function is invoked with one argument: a comment or suggestion creation date.
```
// You can use any other library, like moment.js.
import { DateTime } from 'luxon';
// ...
const editorConfig = {
// ...
locale: {
dateTimeFormat: date => DateTime.fromJSDate( date ).toFormat( 'dd/LL/yyyy' )
}
// ...
};
ClassicEditor.create( editorConfig );
```
source file: "ckeditor5/latest/features/collaboration/annotations/annotations-custom-template.html"
## Custom template for annotations
A custom template is a middle ground between the default UI and a completely custom UI of your own.
### How it works
This solution lets you alter the HTML structure of comment and suggestion annotations. Thanks to that you can, for example:
* Add new CSS classes or HTML elements to enable more complex styling.
* Re-arrange annotation views.
* Add new UI elements, linked with your custom features.
* Remove UI elements.
### Views for comments and suggestions
The view classes used by default by CKEditor 5 collaboration features are:
* [CommentThreadView](../../../api/module%5Fcomments%5Fcomments%5Fui%5Fview%5Fcommentthreadview-CommentThreadView.html) – Generates the UI for comment thread annotations.
* [CommentView](../../../api/module%5Fcomments%5Fcomments%5Fui%5Fview%5Fcommentview-CommentView.html) – Presents a single comment.
* [SuggestionThreadView](../../../api/module%5Ftrack-changes%5Fui%5Fview%5Fsuggestionthreadview-SuggestionThreadView.html) – Generates the UI for suggestion annotations.
These closed-source classes are exported by the `ckeditor5-premium-features` package on npm ([learn more](#ckeditor5/latest/features/collaboration/comments/comments-integration.html--setting-up-a-sample-project)).
### Changing the view template
To use a custom view template, you need to:
1. Create a new view class by extending the default view class.
2. Provide (or extend) the template by overwriting the [getTemplate()](../../../api/module%5Fcomments%5Fcomments%5Fui%5Fview%5Fcommentview-CommentView.html#function-getTemplate) method.
3. Set your custom view class through the editor configuration. We recommend setting it in the default configuration.
Creating a custom view:
Using the custom view in the editor configuration:
```
// ...
const editorConfig = {
// ...
comments: {
CommentView: MyCommentView
},
// ...
};
ClassicEditor.create( editorConfig );
```
Custom [comment](../../../api/module%5Fcomments%5Fconfig-CommentsConfig.html#member-CommentThreadView) and [suggestion](../../../api/module%5Ftrack-changes%5Ftrackchangesconfig-TrackChangesConfig.html#member-SuggestionThreadView) thread views can be set in a similar way:
```
// ...
const editorConfig = {
// ...
comments: {
CommentThreadView: MyCommentThreadView,
},
trackChanges: {
SuggestionThreadView: MySuggestionThreadView
},
// ...
};
ClassicEditor.create( editorConfig );
```
### Example: Adding a new button
Below is an example of how you can provide a simple custom feature that requires creating a new UI element and additional styling.
The proposed feature should allow for marking some comments as important. An important comment should have a yellow border on the right.
To bring this feature, you need:
* A CSS class to change the border of an important comment.
* A button to toggle the comment model state.
* An integration between the model state and the view template.
#### Styling for an important comment
This step is easy. Add the following styles to the `style.css` file in your project:
```
/* style.css */
/* Yellow border for an important comment. */
.ck-comment--important {
border-right: 3px solid hsl( 55, 98%, 48% );
}
```
#### Creating a button and adding it to the template
In this step, you will add some new elements to the template.
Keep in mind that you do not need to overwrite the whole template. You can extend it instead.
#### Enabling the custom view
The custom view will be enabled in editor configuration, as [shown earlier](#ckeditor5/latest/features/collaboration/annotations/annotations-custom-template.html--changing-the-view-template).
```
// main.js
// ...
const editorConfig = {
// ...
comments: {
CommentView: ImportantCommentView,
editorConfig: {
plugins: [ Essentials, Paragraph, Bold, Italic ]
}
}
};
ClassicEditor.create( editorConfig );
```
#### Live demo
Editor output:
```
```
Threads:
```
```
source file: "ckeditor5/latest/features/collaboration/annotations/annotations-custom-theme.html"
## Custom annotation theme
This is the simplest way to change the look of annotations. Using the power of [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using%5FCSS%5Fvariables), it is really easy to override the default design of comments. You can undo this by adding an extra `.css` file.
The image above shows you which variables are responsible for every component of the default annotation view.
### Example of comments customization with CSS variables
With the [inheritance of CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using%5FCSS%5Fcustom%5Fproperties#Inheritance%5Fof%5Fcustom%5Fproperties), you can change the default `:root` values of the variables in the `.ck-sidebar` scope. You can override these properties with a `.css` file or place your customizations directly into the `` section of your page, but in this case, you will need to use a more specific CSS selector than `:root` (for example, ``).
```
/* Change the default yellow color of the comment marker in the content to green. */
:root {
--ck-color-comment-marker: hsl(127, 98%, 83%);
--ck-color-comment-marker-active: hsl(127, 98%, 68%);
}
.ck-sidebar {
--ck-color-comment-background: #ecf5f0;
--ck-color-comment-separator: #64ca6d;
--ck-color-comment-remove-background: #eccbcb;
--ck-comment-content-font-family: Arial;
--ck-comment-content-font-size: 13px;
--ck-comment-content-font-color: #333;
--ck-color-comment-count: #807e81;
--ck-color-annotation-icon: #0f5c2f;
--ck-color-annotation-info: #1eb35c;
--ck-annotation-button-size: 0.85em;
--ck-user-avatar-background: #239855;
}
/* You can even change the appearance of a single element. */
.ck-sidebar .ck-comment__wrapper:first-of-type {
--ck-color-annotation-info: #8e9822;
--ck-user-avatar-background: #8e9822;
}
```
The examples above will generate the following designs for comments:
source file: "ckeditor5/latest/features/collaboration/annotations/annotations-custom-view.html"
## Custom view for annotations
A custom view is the most powerful way to customize annotations. In this case, you need to provide the whole view: a template, UI elements, and any necessary behavior logic, but you can still use some of the default building blocks.
### Using base views
Providing a custom view is based on the same solution as [providing a custom template](#ckeditor5/latest/features/collaboration/annotations/annotations-custom-template.html). You will need to create your own class for the view. In this case, you will be interested in extending base view classes:
* [BaseCommentThreadView](../../../api/module%5Fcomments%5Fcomments%5Fui%5Fview%5Fbasecommentthreadview-BaseCommentThreadView.html) – Base view for comment thread views.
* [BaseCommentView](../../../api/module%5Fcomments%5Fcomments%5Fui%5Fview%5Fbasecommentview-BaseCommentView.html) – Base view for comment views.
* [BaseSuggestionThreadView](../../../api/module%5Ftrack-changes%5Fui%5Fview%5Fbasesuggestionthreadview-BaseSuggestionThreadView.html) – Base view for suggestion thread views.
Base view classes provide some core functionality that is necessary for the view to operate, no matter what the view looks like or what its template is.
The default view classes also extend the base view classes.
### Default view template
The default template used to create comment thread views is shown here: [CommentThreadView#getTemplate()](../../../api/module%5Fcomments%5Fcomments%5Fui%5Fview%5Fcommentthreadview-CommentThreadView.html#function-getTemplate).
The default template used to create suggestion thread views is shown here: [SuggestionThreadView#getTemplate()](../../../api/module%5Ftrack-changes%5Fui%5Fview%5Fsuggestionthreadview-SuggestionThreadView.html#function-getTemplate).
### Creating and enabling a custom view
As a reminder, the code snippet below shows how to enable a custom view for CKEditor 5 collaboration features:
The only obligatory action that needs to be done in your custom view constructor is setting up a template:
```
class CustomCommentThreadView extends BaseCommentThreadView {
constructor( ...args ) {
super( ...args );
this.setTemplate( {
// Template definition here.
// ...
} );
}
}
```
It is your responsibility to construct the template and all the UI elements that are needed for the view.
#### Reading data and binding with template
Your view is passed a model object that is available under the `_model` property. It should be used to set (or bind to) the initial data of your view’s properties.
Some of your view’s properties may be used to control the view template. For example, you can bind a view property so that when it is set to `true`, the template main element will receive an additional CSS class. To bind view properties with a template, the properties need to be [observable](../../../api/module%5Futils%5Fobservablemixin-Observable.html#function-set:KEY%5FVALUE). Of course, you can bind already existing observable properties with your template.
You can [bind two observable properties](../../../api/module%5Futils%5Fobservablemixin-Observable.html#function-bind:SINGLE%5FBIND) in a way that the value of one property will depend on the other. You can also directly [listen to the changes of an observable property](../../../api/module%5Futils%5Fobservablemixin-Observable.html#event-change:%7Bproperty%7D).
```
const bind = this.bindTemplate;
// Set an observable property.
this.set( 'isImportant', false );
// More code.
// ...
this.setTemplate( {
tag: 'div',
attributes: {
class: [
// Bind the new observable property with the template.
bind.if( 'isImportant', 'ck-comment--important' ),
// Bind an existing observable property with the template.
bind.if( 'isDirty', 'ck-comment--unsaved' )
]
}
} );
```
#### Performing actions
The view needs to communicate with other parts of the system. When a user performs an action, something needs to be executed, for example: a comment should be removed. This communication is achieved by firing events (with appropriate data). See the example below:
```
this.removeButton = new ButtonView();
this.removeButton.on( 'execute', () => this.fire( 'removeCommentThread' ) );
```
The list of all events that a given view class can fire is available in the [API documentation of the BaseCommentThreadView class](../../../api/module%5Fcomments%5Fcomments%5Fui%5Fview%5Fbasecommentthreadview-BaseCommentThreadView.html).
### Example: Comment thread actions dropdown
In this example, you will create a custom comment thread view with action buttons (edit, remove) moved to a dropdown UI element. The dropdown will be added inside a new element, and placed above the thread UI.
#### Creating a custom thread view with a new template
First, create a foundation for your custom solution:
Then, you need to create a dropdown UI element and fill it with items:
Note that the dropdown should not be visible if the current local user is not the author of the thread.
Since the first comment in the comment thread represents the whole thread, you can base it on the properties of the first comment.
If there are no comments in the thread, it means that this is a new thread so the local user is the author.
```
class CustomCommentThreadView extends BaseCommentThreadView {
constructor( ...args ) {
super( ...args );
const bind = this.bindTemplate;
// The template definition is partially based on the default comment thread view.
const templateDefinition = {
tag: 'div',
attributes: {
class: [
'ck',
'ck-thread',
'ck-reset_all-excluded',
'ck-rounded-corners',
bind.if( 'isActive', 'ck-thread--active' )
],
// Needed for the native DOM Tab key navigation.
tabindex: 0,
role: 'listitem',
'aria-label': bind.to( 'ariaLabel' ),
'aria-describedby': this.ariaDescriptionView.id
},
children: [
{
tag: 'div',
attributes: {
class: 'ck-thread__container'
},
children: [
this.commentsListView,
this.commentThreadInputView
]
}
]
};
const isNewThread = this.length == 0;
const isAuthor = isNewThread || this._localUser == this._model.comments.get( 0 ).author;
// Add the actions dropdown only if the local user is the author of the comment thread.
if ( isAuthor ) {
templateDefinition.children.unshift(
{
tag: 'div',
attributes: {
class: 'ck-thread-top-bar'
},
children: [
this._createActionsDropdown()
]
}
);
}
this.setTemplate( templateDefinition );
}
// ...
}
```
As far as disabling the UI is concerned, the actions in the dropdown should be disabled if the comment thread is in the read-only mode. Also, the edit button should be hidden if there are no comments in the thread. Additionally, the resolve/reopen button should be displayed based on the comment thread resolve state.
```
// main.js
class CustomCommentThreadView extends BaseCommentThreadView {
// ...
_createActionsDropdown() {
// ...
const editButtonModel = new UIModel( {
withText: true,
label: 'Edit',
action: 'edit'
} );
// The button should be enabled when the read-only mode is off.
// So, `isEnabled` should be a negative of `isReadOnly`.
editButtonModel.bind( 'isEnabled' )
.to( this._model, 'isReadOnly', isReadOnly => !isReadOnly );
// Hide the button if the thread has no comments yet.
editButtonModel.bind( 'isVisible' )
.to( this, 'length', length => length > 0 );
items.add( {
type: 'button',
model: editButtonModel
} );
const resolveButtonModel = new UIModel( {
withText: true,
label: 'Resolve',
action: 'resolve'
} );
// Hide the button if the thread is resolved or cannot be resolved.
resolveButtonModel.bind( 'isVisible' )
.to( this._model, 'isResolved', this._model, 'isResolvable',
( isResolved, isResolvable ) => !isResolved && isResolvable );
items.add( {
type: 'button',
model: resolveButtonModel
} );
const reopenButtonModel = new UIModel( {
withText: true,
label: 'Reopen',
action: 'reopen'
} );
// Hide the button if the thread is not resolved or cannot be resolved.
reopenButtonModel.bind( 'isVisible' )
.to( this._model, 'isResolved', this._model, 'isResolvable',
( isResolved, isResolvable ) => isResolved && isResolvable );
items.add( {
type: 'button',
model: reopenButtonModel
} );
const removeButtonModel = new UIModel( {
withText: true,
label: 'Delete',
action: 'delete'
} );
removeButtonModel.bind( 'isEnabled' )
.to( this._model, 'isReadOnly', isReadOnly => !isReadOnly );
items.add( {
type: 'button',
model: removeButtonModel
} );
}
}
```
Finally, some styling will be required for the new UI elements:
```
/* style.css */
/* ... */
.ck-thread-top-bar {
padding: 2px 4px 3px 4px;
background: #404040;
text-align: right;
}
.ck-thread-top-bar .ck.ck-dropdown {
font-size: 14px;
width: 100px;
}
.ck-thread-top-bar .ck.ck-dropdown .ck-button.ck-dropdown__button {
color: #000000;
background: #EEEEEE;
}
```
#### Linking buttons with actions
The edit button should turn the first comment into edit mode:
```
dropdownView.on( 'execute', evt => {
const action = evt.source.action;
if ( action == 'edit' ) {
this.commentsListView.commentViews.get( 0 ).switchToEditMode();
}
// More actions.
// ...
} );
```
The delete button should remove the comment thread.
As described earlier, your view should fire events to communicate with other parts of the system:
```
dropdownView.on( 'execute', evt => {
const action = evt.source.action;
if ( action == 'edit' ) {
this.commentsListView.commentViews.get( 0 ).switchToEditMode();
}
if ( action == 'delete' ) {
this.fire( 'removeCommentThread' );
}
if ( action == 'resolve' ) {
this.fire( 'resolveCommentThread' );
}
if ( action == 'reopen' ) {
this.fire( 'reopenCommentThread' );
}
if ( action == 'resolve' ) {
this.fire( 'resolveCommentThread' );
}
if ( action == 'reopen' ) {
this.fire( 'reopenCommentThread' );
}
} );
```
#### Altering the first comment view
Your new custom comment thread view is ready.
For comment views, you will use the default comment views. However, there is one thing you need to take care of. Since you moved comment thread controls to a separate dropdown, you should hide these buttons from the first comment view.
This modification will be added in a custom comment thread view. It should not be done in a custom comment view because that would have an impact on comments in suggestion threads.
The first comment view can be obtained from the [commentsListView](../../../api/module%5Fcomments%5Fcomments%5Fui%5Fview%5Fbasecommentthreadview-BaseCommentThreadView.html#member-commentsListView) property. If there are no comments yet, you can listen to the property and apply the custom behavior when the first comment view is added.
```
// main.js
class CustomCommentThreadView extends BaseCommentThreadView {
constructor( ...args ) {
// More code.
// ...
if ( this.length > 0 ) {
// If there is a comment when the thread is created, apply custom behavior to it.
this._modifyFirstCommentView();
} else {
// If there are no comments (an empty thread was created by the user),
// listen to `this.commentsListView` and wait for the first comment to be added.
this.listenTo( this.commentsListView.commentViews, 'add', evt => {
// And apply the custom behavior when it is added.
this._modifyFirstCommentView();
evt.off();
} );
}
}
// More code.
// ...
_modifyFirstCommentView() {
// Get the first comment.
const commentView = this.commentsListView.commentViews.get( 0 );
// By default, the comment button is bound to the model state
// and the buttons are visible only if the current local user is the author.
// You need to remove this binding and make buttons for the first
// comment always invisible.
commentView.removeButton.unbind( 'isVisible' );
commentView.removeButton.isVisible = false;
commentView.editButton.unbind( 'isVisible' );
commentView.editButton.isVisible = false;
}
}
```
#### Final solution
Below you can find the final code for the created components:
```
/* style.css */
/* ... */
.ck-thread-top-bar {
padding: 2px 4px 3px 4px;
background: #404040;
text-align: right;
}
.ck-thread-top-bar .ck.ck-dropdown {
font-size: 14px;
width: 100px;
}
.ck-thread-top-bar .ck.ck-dropdown .ck-button.ck-dropdown__button {
color: #000000;
background: #EEEEEE;
}
```
#### Live demo
source file: "ckeditor5/latest/features/collaboration/annotations/annotations-display-mode.html"
## Annotations display mode
There are three built-in UIs to display comment threads and suggestion annotations: the wide sidebar, the narrow sidebar, and inline balloons. You can also display them together in more advanced scenarios where various annotation sources (comments, suggestions) are connected to different UIs, or even create your own UI for annotations.
### Inline balloons
Inline balloon display mode is designed for narrow screens like mobile devices and UIs where the WYSIWYG editor is used to edit a small part of the content.
Inline display mode is the default solution. It is used when the sidebar configuration is not specified.
Even if the sidebar configuration is set, you can still dynamically switch to the inline display mode by calling the [switchTo()](../../../api/module%5Fcomments%5Fannotations%5Fannotationsuis-AnnotationsUIs.html#function-switchTo) method with the `'inline'` argument:
```
// ...
ClassicEditor
.create( editorConfig )
.then( editor => {
editor.plugins.get( 'AnnotationsUIs' ).switchTo( 'inline' );
// The sidebar container is not removed automatically,
// so it is up to your integration to hide it (or manage in another way).
document.querySelector( '.editor-container__sidebar' ).style.display = 'none';
} );
```
### Wide sidebar
The wide sidebar can fit the largest amount of information. In this mode the user can see the beginning and the end of each discussion, as well as the entire discussion for the currently selected marker. It is the recommended solution whenever you have enough space for it.
To use the wide sidebar for displaying comments and suggestion annotations, first, prepare a proper HTML structure:
```
CKEditor 5 Sample
```
Then update CSS styles to display the sidebar on the right side of the editor:
```
.editor-container {
--ckeditor5-preview-sidebar-width: 270px;
}
.editor-container__editor-wrapper {
display: flex;
width: fit-content;
}
.editor-container--classic-editor .editor-container__editor {
min-width: 795px;
max-width: 795px;
}
.editor-container__sidebar {
min-width: var(--ckeditor5-preview-sidebar-width);
max-width: var(--ckeditor5-preview-sidebar-width);
margin-top: 28px;
margin-left: 10px;
margin-right: 10px;
}
```
Then, initialize the rich text editor. In the configuration, set the editor to use the `` element as the comments container.
```
// ...
const editorConfig = {
// ...
sidebar: {
container: document.querySelector('#editor-annotations')
},
// ...
}
ClassicEditor.create( editorConfig );
```
After setting the configuration as shown in the example above, the wide sidebar display mode will be used. If the display mode was changed, you can change it back by calling [switchTo()](../../../api/module%5Fcomments%5Fannotations%5Fannotationsuis-AnnotationsUIs.html#function-switchTo):
```
ClassicEditor
.create( editorConfig )
.then( editor => {
editor.plugins.get( 'AnnotationsUIs' ).switchTo( 'wideSidebar' );
} );
```
You can also set the sidebar container dynamically using the [setContainer()](../../../api/module%5Fcomments%5Fannotations%5Fsidebar-Sidebar.html#function-setContainer) method:
```
ClassicEditor
.create( editorConfig )
.then( editor => {
editor.plugins.get( 'Sidebar' ).setContainer( element );
} );
```
If the sidebar container has already been set, all the items inside it will be moved to the new container.
### Narrow sidebar
The narrow sidebar is a compromise between the wide sidebar and the inline balloons. It does not take as much space as the wide sidebar but contains more information than inline annotations. The user will immediately see when multiple comment threads are added to the same spot as well as how many comments are added.
The HTML structure for the wide and narrow sidebars is similar. The only difference is that you need to set a different `min-width` CSS property for the `#editor-annotations` element:
```
/* ... */
.editor-container__sidebar {
min-width: 65px;
}
/* ... */
```
Then, initialize the editor and switch the UI to the `narrowSidebar` mode using the [switchTo()](../../../api/module%5Fcomments%5Fannotations%5Fannotationsuis-AnnotationsUIs.html#function-switchTo) method. Note that you need to switch the UI type manually since the wide sidebar will be displayed by default.
```
// ...
const editorConfig = {
// ...
sidebar: {
container: document.querySelector('#editor-annotations')
},
// ...
}
ClassicEditor
.create( editorConfig )
.then( editor => {
editor.plugins.get( 'AnnotationsUIs' ).switchTo( 'narrowSidebar' );
} );
```
### Multiple UIs
Annotations were designed to support displaying various annotations UIs at the same time. This allows you to display different annotation sources in various places, for example, displaying comments in the wide sidebar while showing inline balloons for suggestions.
To activate multiple UIs at the same time, the filtering function should be passed to the [activate()](../../../api/module%5Fcomments%5Fannotations%5Fannotationsuis-AnnotationsUIs.html#function-activate) method. The function specifies which annotations are controlled by a given UI. Note that one annotation cannot be managed by multiple [AnnotationsUI](../../../api/module%5Fcomments%5Fannotations%5Fannotationsuis-AnnotationsUI.html) at the same time. If an annotation is not accepted by any of annotations UIs, then that annotation will not be shown.
To use a combination of annotations UIs for displaying comments and suggestion annotations, first prepare a proper HTML structure (for demonstration purposes, the wide sidebar is used):
```
```
Then, initialize the rich text editor using a preset that includes both the comments and track changes features. You can get the necessary code from the [track changes integration](#ckeditor5/latest/features/collaboration/track-changes/track-changes-integration.html) guide. Then tweak the code to use two annotations UIs as shown below:
```
// ...
ClassicEditor
.create( editorConfig )
.then( editor => {
const annotationsUIs = editor.plugins.get( 'AnnotationsUIs' );
// Deactivate all UIs first as the `activate()` method might not deactivate all UIs.
annotationsUIs.deactivateAll();
annotationsUIs.activate( 'wideSidebar', annotation => annotation.type === 'comment' );
annotationsUIs.activate( 'inline', annotation => annotation.type !== 'comment' );
} );
```
### All display modes in action
The code snippet below allows for switching between all available display modes.
In the `index.html` file obtained from the Builder, add the following markup:
```
```
Then, in the `style.css` file, add the following styles
```
.editor-container__sidebar {
transition: min-width .4s ease-out-in;
}
.editor-container__sidebar.narrow {
min-width: 65px;
}
.editor-container__sidebar.hidden {
display: none;
}
```
Finally, add the following code in the `main.js` file:
```
ClassicEditor
.create( editorConfig )
.then( editor => {
const annotationsUIs = editor.plugins.get( 'AnnotationsUIs' );
const annotationsContainer = document.querySelector( '.editor-container__sidebar' );
const inlineButton = document.querySelector( '#inline' );
const narrowButton = document.querySelector( '#narrow' );
const wideButton = document.querySelector( '#wide' );
const wideAndInlineButton = document.querySelector( '#wide-inline' );
function markActiveButton( button ) {
[ inlineButton, narrowButton, wideButton, wideAndInlineButton ]
.forEach( el => el.classList.toggle( 'active', el === button ) );
}
function switchToInline() {
markActiveButton( inlineButton );
annotationsContainer.classList.remove( 'narrow' );
annotationsContainer.classList.add( 'hidden' );
annotationsUIs.switchTo( 'inline' );
}
function switchToNarrowSidebar() {
markActiveButton( narrowButton );
annotationsContainer.classList.remove( 'hidden' );
annotationsContainer.classList.add( 'narrow' );
annotationsUIs.switchTo( 'narrowSidebar' );
}
function switchToWideSidebar() {
markActiveButton( wideButton );
annotationsContainer.classList.remove( 'narrow', 'hidden' );
annotationsUIs.switchTo( 'wideSidebar' );
}
function switchToWideSidebarAndInline() {
markActiveButton( wideAndInlineButton );
annotationsContainer.classList.remove( 'narrow', 'hidden' );
annotationsUIs.deactivateAll();
annotationsUIs.activate( 'wideSidebar', annotation => annotation.type === 'comment' );
annotationsUIs.activate( 'inline', annotation => annotation.type !== 'comment' );
}
editor.ui.view.listenTo( inlineButton, 'click', () => switchToInline() );
editor.ui.view.listenTo( narrowButton, 'click', () => switchToNarrowSidebar() );
editor.ui.view.listenTo( wideButton, 'click', () => switchToWideSidebar() );
editor.ui.view.listenTo( wideAndInlineButton, 'click', () => switchToWideSidebarAndInline() );
// Set wide sidebar as default.
switchToWideSidebar();
} )
.catch( error => console.error( error ) );
```
#### Demo
The following sample showcases the snippet above:
### Custom UI
In addition to the built-in annotations UIs, it is also possible to create a custom UI that will display annotations in a way that is better suited to your application.
Note that annotations UI should implement the [AnnotationsUI](../../../api/module%5Fcomments%5Fannotations%5Fannotationsuis-AnnotationsUI.html) interface.
The frame of an annotations UI is presented below. For this to work, it must be included in the list of editor plugins and activated. These are the changes that you will have to make to the `main.js` file of your project:
source file: "ckeditor5/latest/features/collaboration/annotations/annotations.html"
## Annotations in CKEditor 5 collaboration features
Annotations are a part of the collaboration features system. They are UI elements (“balloons”) that correspond to comments and suggestions.
### Additional feature information
Features like comments and track changes create views (“balloons”) that represent their data. Such a view is called an annotation. They are added to and stored in the annotations plugin. Then, UI mechanisms (like sidebars or inline annotations) use the annotation views to populate themselves and create various types of user experiences.
Using the annotations system and the provided API, you can:
* [Choose between the provided display modes (sidebars or inline annotations)](#ckeditor5/latest/features/collaboration/annotations/annotations-display-mode.html).
* [Customize annotations created by comments and track changes plugins](#ckeditor5/latest/features/collaboration/annotations/annotations.html--annotations-customization).
* Provide custom annotations for your plugins.
* Provide custom UI for annotations (for example, a custom sidebar with your display logic).
### Annotations customization
There are multiple levels on which you can modify the look of annotations:
* [Theme customization](#ckeditor5/latest/features/collaboration/annotations/annotations-custom-theme.html).
* [Configuration, including comment input field configuration](#ckeditor5/latest/features/collaboration/annotations/annotations-custom-configuration.html).
* [Providing a custom template for the default views](#ckeditor5/latest/features/collaboration/annotations/annotations-custom-template.html).
* [Providing a custom view for annotations](#ckeditor5/latest/features/collaboration/annotations/annotations-custom-view.html).
Refer to the linked guides to learn more about how to customize annotations for collaboration features of CKEditor 5.
### API overview
The main entry point for all external actions should be the [Annotations](../../../api/module%5Fcomments%5Fannotations%5Fannotations-Annotations.html) plugin. It stores [annotations](../../../api/module%5Fcomments%5Fannotations%5Fannotation-Annotation.html) for all editors and allows manipulating them.
In this example, the [Annotation plugin API](../../../api/module%5Fcomments%5Fannotations%5Fannotations-Annotations.html) will be used to display a custom annotation. To do that, you should create a target element to which the annotation will be attached. In the `index.html` file created in the [reference](#ckeditor5/latest/features/collaboration/comments/comments-integration.html--before-you-start) guide, add the following static `` element next to the editor data container:
```
Custom annotation target
```
Now, in the `main.js` file of the project, please add the following code that creates the annotation:
When you run the project, you should see the “Custom annotation target” element displayed below the editor. You should also see the annotation view with the “Annotation text” displayed in the sidebar.
source file: "ckeditor5/latest/features/collaboration/collaboration.html"
## Collaboration overview
The CKEditor 5 architecture was designed to bring collaborative editing features where many authors can work on the same rich text documents.
### Demo
Use the set of collaboration features in the demo below: turn on tracking changes , add comments , check the comments archive , and follow the revision history of the document.
This demo presents a limited set of features. Visit the [feature-rich editor example](#ckeditor5/latest/examples/builds-custom/full-featured-editor.html) to see more in action.
### Available collaboration features
The collaboration capabilities are provided by three easy-to-integrate plugins delivering different features: comments, track changes, and revision history. You will find more information about each feature in the dedicated guides. You may also look at some interesting details and examples in the [Collaborative writing in CKEditor 5](https://ckeditor.com/blog/Feature-of-the-month-Collaborative-writing-in-CKEditor-5/) blog post after reading these guides.
You can use these features standalone or together, depending on the users’ needs. The collaboration can also be [either in real time or asynchronous](#ckeditor5/latest/features/collaboration/collaboration.html--real-time-vs-asynchronous-collaboration).
#### Comments
Thanks to the comments feature, the users can add sidenotes to marked fragments of the document, including text and block elements such as images. It also allows the users to discuss in threads and remove comments when they finish the discussion.
You can define where you want to store the comments data. To load and save it, you will also need to create a proper [integration with your database](#ckeditor5/latest/features/collaboration/comments/comments-integration.html). If you want to automatically synchronize the comments discussion between users, you can also use comments as a part of the real-time collaboration.
You can display comment threads in a sidebar or inline:
Moreover, you can resolve comment threads, which moves them to the archive. Note that the comments archive is enabled by default and cannot be turned off.
Refer to the [Comments](#ckeditor5/latest/features/collaboration/comments/comments.html) guide for more information.
#### Track changes
The track changes feature brings automatic suggestion marking for the document as you change it. When editing the document, the user can switch to the track changes mode. All their changes will then create suggestions that they can accept or discard.
You can define where you want to store the suggestions data. To load and save it, you will also need to create a proper [integration with your database](#ckeditor5/latest/features/collaboration/track-changes/track-changes-integration.html). If you want to automatically synchronize the suggestions between users, you can also use track changes as a part of the real-time collaboration.
You can display suggestion annotations in a sidebar or inline:
Refer to the [Track changes](#ckeditor5/latest/features/collaboration/track-changes/track-changes.html) guide for more information.
#### Revision history
The revision history feature is a document versioning tool. It allows CKEditor 5 users to create and view the chronological revision history of their content. These versions are listed in the side panel. The preview mode allows for easy viewing of content development between revisions. You can rename, compare, and restore older revisions on the go.
Refer to the [revision history](#ckeditor5/latest/features/collaboration/revision-history/revision-history.html) guide for more information.
### Real-time vs asynchronous collaboration
There are two available collaboration modes in CKEditor 5: real-time collaboration (often referred to as RTC) and asynchronous collaboration. Both collaborative workflows allow your users to work together within a single application, without the need for third-party tools. They can either collaborate on documents asynchronously or use a real-time editor to write, review, and comment on content in live mode. You can use all available CKEditor 5 collaboration plugins in both modes.
| **Asynchronous vs real-time collaboration comparison** | | |
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- |
| Collaboration type | **Asynchronous** | **Real-time** |
| Workflow | Sequential | Simultaneous |
| Features included | Revision history, track changes, and comments. | Revision history, track changes, and comments working in real time. |
| Backend | Custom backend provided by the customer. | Backend provided by CKEditor in on-premises and SaaS modes. |
| Conflict solving | Not implemented. | Automatically solves all conflicts if users make changes at the same time. |
| Integration tasks for system developers | Write backend endpoints to save and load data.Write frontend adapters to pass the data to backend endpoints. | Just configure tokens for the SaaS version.For on-premises, also set up the infrastructure. |
#### Asynchronous collaboration
Asynchronous collaboration is perfect for linear workflow, where users create, review, and edit content sequentially and there is no need for them to work simultaneously. It suits professional environments working on business deals, legal documents, academic research papers, contract management, and more use cases.
In this mode, a single author can work on the document, using the revision history, track changes and comments features to interact with previous and following editors. All work is done sequentially.
The asynchronous approach can be more cost-effective and it requires a less dedicated infrastructure. It also gives you full control over your data. Because you are fully responsible for loading, saving, and storing the data, it is the on-premises version by default. On the other hand, this approach requires you to maintain both frontend and backend integration code, also in a situation when the editor features are updated.
#### Real-time collaboration
In real-time collaboration, on the other hand, many users can work simultaneously on the same document, even on the same part of it, with no content locking. Comments and track changes are synchronized automatically between users, on the go. It automatically solves all conflicts that may occur if users make changes at the same time. The editor also lists all users currently involved in the editing process.
Thanks to this, collaborating users will not only be able to edit a rich text document at the same time but also discuss the process live in comments. They can also save revisions. This is perfect for fast-paced content-creation situations and it can still prove useful in a single-user mode, just like the asynchronous solution.
Real-time collaboration comes with a ready-to-use frontend integration and a backend solution. You can use it as SaaS with CKEditor Cloud Services or install on your machines in the on-premises version.
CKEditor provides both, so there is no need for a complicated integration. Import the plugins, fill in the editor configuration, and provide the token configuration. The on-premises solution requires some minimal extra setup.
You can still maintain control over your data. REST APIs will allow you to copy whatever data you stored on our servers and more!
Refer to the [Real-time collaboration](#ckeditor5/latest/features/collaboration/real-time-collaboration/real-time-collaboration.html) guide for more information.
source file: "ckeditor5/latest/features/collaboration/comments/comments-archive.html"
## Comments archive custom UI
By default, the comments archive dropdown panel is displayed in the toolbar by adding the `'commentsArchive'` button in the toolbar configuration. Refer to the [comments](#ckeditor5/latest/features/collaboration/comments/comments.html) guide for more information.
In this guide, you will learn how to prepare a custom comments archive UI in your application and display it in the container of your choice.
### Before you start
For the purpose of this guide, the CKEditor Cloud Services and the [real-time collaborative comments](#ckeditor5/latest/features/collaboration/real-time-collaboration/real-time-collaboration-integration.html) feature will be used. However, the comments feature API can also be used in a similar way together with the [standalone comments](#ckeditor5/latest/features/collaboration/comments/comments-integration.html) feature.
Make sure that your editor is properly integrated with the comments feature before moving on.
### Preparing the HTML structure
In this guide, we will prepare an editor integration with a custom side panel.
There will be two tabs that will let the user switch between what is displayed in the side panel. By default, the side panel will display the editor’s regular wide sidebar. The other tab will switch the side panel content to display the resolved comments.
First, adjust the HTML structure by extending the `div.editor-container__sidebar` container:
```
...
...
```
Then add styles for the side panel:
```
```
### Implementing the custom comments archive UI plugin
Now, create a plugin that will use the provided HTML structure and fill the comments archive container with resolved comment threads.
The behavior for the tabs will be implemented as simple DOM event listeners.
You can observe changes on the [CommentsArchiveUI#annotationViews](../../../api/module%5Fcomments%5Fcomments%5Fcommentsarchiveui-CommentsArchiveUI.html#member-annotationViews) collection to fill the comments archive tab content.
Additionally, for a better user experience, as long as the comments archive is shown in the side panel, the annotations for regular comment threads will be displayed in the inline display mode. This will give the users access to the regular comment threads data also when the archive is open.
```
class CustomCommentsArchiveUI extends Plugin {
static get requires() {
// We will use a property from the `CommentsArchiveUI` plugin, so add it to requires.
return [ 'CommentsArchiveUI' ];
}
init() {
this.tabs = document.querySelectorAll( '.tabs__item' );
this.sidebars = document.querySelectorAll( '.sidebar' );
// Switch the side panel to the appropriate tab after clicking it.
this.tabs.forEach( item => {
item.addEventListener( 'click', () => this.handleTabClick( item ) );
} );
this.initCommentsArchive();
}
// Switches between the active tabs.
// Shows appropriate tab container and set the CSS classes to reflect the changes.
handleTabClick( tabElement ) {
if ( tabElement.classList.contains( 'active' ) ) {
return;
}
const annotationsUIs = this.editor.plugins.get( 'AnnotationsUIs' );
const targetId = tabElement.dataset.target;
const sidebarContainer = document.getElementById( targetId );
this.tabs.forEach( item => {
item.classList.remove( 'active' );
} );
this.sidebars.forEach( item => {
item.classList.remove( 'active' );
} );
tabElement.classList.add( 'active' );
sidebarContainer.classList.add( 'active' );
const isCommentsArchiveOpen = targetId === 'archive';
// If the comments archive is open, switch the display mode for comments to "inline".
//
// This way the annotations for regular comments threads will be displayed next to them
// when a user clicks on the comment thread marker.
//
// When the comments archive is closed, switch back to displaying comments annotations in the wide sidebar.
annotationsUIs.switchTo( isCommentsArchiveOpen ? 'inline' : 'wideSidebar' );
}
initCommentsArchive() {
// Container for the resolved comment threads annotations.
const commentsArchiveList = document.querySelector( '.comments-archive__list' );
// The `CommentsArchiveUI` plugin handles all annotation views that can be used
// to render resolved comment threads inside the comments archive container.
const commentsArchiveUI = this.editor.plugins.get( 'CommentsArchiveUI' );
// First, handle the initial resolved comment threads.
for ( const annotationView of commentsArchiveUI.annotationViews ) {
commentsArchiveList.appendChild( annotationView.element );
}
// Handler to append new resolved thread inside the comments archive custom view.
commentsArchiveUI.annotationViews.on( 'add', ( _, annotationView ) => {
if ( !commentsArchiveList.contains( annotationView.element ) ) {
commentsArchiveList.appendChild( annotationView.element );
}
} );
// Handler to remove the element when thread has been removed or reopened.
commentsArchiveUI.annotationViews.on( 'remove', ( _, annotationView ) => {
if ( commentsArchiveList.contains( annotationView.element ) ) {
commentsArchiveList.removeChild( annotationView.element );
}
} );
}
}
```
Finally, add the new plugin to the editor.
```
ClassicEditor
.create( {
// ...
plugins: [
// ...
CustomCommentsArchiveUI
]
} )
.then( /* ... */ )
.catch( /* ... */ );
```
### Demo
Click the “add comment” button in the toolbar to add a comment thread, then use the “tick” icon to resolve a comment thread. Finally, you can see the resolved comment threads in the “Comments archive” tab.
source file: "ckeditor5/latest/features/collaboration/comments/comments-integration.html"
## Integrating comments with your application
The comments feature [provides an API](../../../api/comments.html) that lets you add, remove, and update comments in the editor. To save and access all these changes in your database, you first need to integrate this feature.
### Integration methods
This guide will discuss two ways to integrate CKEditor 5 with your comments data source:
* [A simple “load and save” integration](#ckeditor5/latest/features/collaboration/comments/comments-integration.html--a-simple-load-and-save-integration) using directly the `CommentsRepository` plugin API.
* [An adapter integration](#ckeditor5/latest/features/collaboration/comments/comments-integration.html--adapter-integration) which updates the comments data immediately in the database when it changes in the editor.
The adapter integration is the recommended one because it gives you better control over the data.
### Before you start
#### Preparing a custom editor setup
To use the comments plugin, you need to prepare a custom editor setup with the asynchronous version of the comments feature included.
The easiest way to do that is by using the [Builder](https://ckeditor.com/ckeditor-5/builder/?redirect=docs). Pick a preset and start customizing your editor.
**In the “Features” section** of the Builder (2nd step), make sure to:
* turn off the “real-time” toggle next to the “Collaboration” group,
* enable the “Collaboration → Comments” feature.
Once you finish the setup, the Builder will provide you with the necessary HTML, CSS, and JavaScript code snippets. We will use those code snippets in the next step.
#### Setting up a sample project
Once we have a custom editor setup we need a simple JavaScript project to run it. For this, we recommend cloning the basic project template from our repository:
```
npx -y degit ckeditor/ckeditor5-tutorials-examples/sample-project sample-project
cd sample-project
npm install
```
Then, install the necessary dependencies:
```
npm install ckeditor5
npm install ckeditor5-premium-features
```
This project template uses [Vite](https://vitejs.dev/) under the hood and contains 3 source files that we will use: `index.html`, `style.css`, and `main.js`.
It is now the time to use our custom editor setup. **Go to the “Installation” section** of the Builder and copy the generated code snippets to those 3 files.
#### Activating the feature
To use this premium feature, you need to activate it with a license key. Refer to the [License key and activation](#ckeditor5/latest/getting-started/licensing/license-key-and-activation.html) guide for details.
After you have successfully obtained the license key open the `main.js` file and update the `your-license-key` string with your license key.
#### Building the project
Finally, build the project by running:
```
npm run dev
```
When you open the sample in the browser you should see the WYSIWYG editor with the comments plugin. However, it still does not load or save any data. You will learn how to add data to the comments plugin later in this guide.
Let’s now dive deeper into the structure of this setup.
#### Basic setup’s anatomy
Let’s now go through the key fragments of this basic setup.
##### HTML structure
The HTML and CSS structure of the page creates two columns:
* `` is the container used by the editor.
* `