| category | cloud-services | |
|---|---|---|
| order | 30 | |
| meta-title | Cloud Services Server-side Editor API | CKEditor 5 Documentation | |
| meta-description | Learn how to use server-side API to manage content and collaboration data easily without running the editor. | |
| modified_at | 2025-06-05 | |
| badges |
|
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.
The remote script 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.
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.
- 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.
Below, you will find several examples of practical server-side API applications. There are far more possibilities available.
In the examples below we use the {@link module:core/editor/editor~Editor `editor`} variable in many places. While on the client (browser) environment it is not available by default, it is a globally available pointer to the editor's instance on the server. There is no need to set this up when using Cloud Services.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 link Cloud Services Server-side Editor API documentation.
Please note you need to have a bundle uploaded first with proper credentials for the service to work properly.The most basic action you can perform is getting the editor's data.
// Get the editor data.
const data = editor.getData();
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.
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.
To learn more about the commands architecture, visit the Commands documentation guide.
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 = '<h2>New section</h2><p>This is a <strong>new section</strong> inserted into the document using <u>server-side editor API</u>.</p>';
// 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 );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 {@link framework/architecture/editing-engine Editing engine} guide.
You can leverage the {@link features/track-changes track changes} feature API to manage existing content suggestions, retrieve final document data with all suggestions accepted, or implement automated or AI-powered content reviews.
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.
Now, let's see how to suggest deleting a specified part of the document:
// Enable track changes to mark our edits as suggestions.
editor.execute( 'trackChanges' );
// Get the section we want to remove and prepare for deletion.
const firstElement = editor.model.document.getRoot().getChild( 0 );
const deleteRange = editor.model.createRangeIn( firstElement );
const deleteSelection = editor.model.createSelection( deleteRange );
// Remove the content as a suggestion.
editor.model.deleteContent( deleteSelection );This functionality is essential when building, for example, automated content review systems. You might use it to precisely mark any content that should be removed. All content inside deleteSelection will become a deletion suggestion.
You can also suggest adding new content:
// Enable track changes for the new content.
editor.execute( 'trackChanges' );
// Prepare the new content we want to add.
const modelFragment = editor.data.parse( 'Hello <strong>world!</strong>' );
// 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 );The insertContent() method can be used 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.
You can use the {@link module:track-changes/trackchangesdata~TrackChangesData track changes data plugin} 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 of 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.
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 );
}
} );
} );
}The {@link features/comments comments} feature allows your users to have discussions on 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.
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's an example that shows how to automatically add comments to images that are missing the alt attribute:
const model = editor.model;
const range = model.createRangeIn( model.document.getRoot() );
editor.model.change( () => {
for ( const item of range.getItems() ) {
const isImage = item.is( 'element', 'imageBlock' ) || item.is( 'element', 'imageInline' );
if ( isImage && !item.getAttribute( 'alt' ) ) {
const commentRange = model.createRangeOn( item );
const firstCommentMessage = 'The alt attribute is missing.';
// Add a comment on image without ALT attribute.
editor.execute(
'addCommentThread',
{
ranges: [ commentRange ],
comment: firstCommentMessage
}
);
}
}
} );This 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.
You can use the comments feature API to manage existing comments in your documents. For example, here's how 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.
Every comment thread and suggestion is connected with an annotation that is used to display it in the editor. You can get the corresponding UI annotation for a model element, or get the model element for a given annotation using dedicated methods.
The CommentsRepository plugin provides methods to get the corresponding annotation for a comment thread or find a comment thread for a given annotation:
const commentsRepository = editor.plugins.get( 'CommentsRepository' );
// Get an annotation for a comment thread.
const annotation = commentsRepository.getAnnotationForCommentThread( commentThread );
// Get a comment thread for an annotation.
const commentThread = commentsRepository.getCommentThreadForAnnotation( annotation );Similarly, the TrackChangesUI plugin provides methods to work with suggestion annotations:
const trackChangesUI = editor.plugins.get( 'TrackChangesUI' );
// Get an annotation for a suggestion.
const annotation = trackChangesUI.getAnnotationForSuggestion( suggestion );
// Get a suggestion for an annotation.
const suggestion = trackChangesUI.getSuggestionForAnnotation( annotation );Sometimes you might only have access to a collection of annotations without their corresponding elements. Here's how to read the data in such case:
const results = [];
const trackChangesUI = editor.plugins.get( 'TrackChangesUI' );
const commentsRepository = editor.plugins.get( 'CommentsRepository' );
for ( const annotation of editor.plugins.get( 'Annotations' ).collection ) {
if ( annotation.type == 'comment' ) {
const comment = commentsRepository.getCommentThreadForAnnotation( annotation );
const ranges = Array.from( editor.model.markers.getMarkersGroup( 'comment:' + comment.id ) )
.map( marker => marker.getRange() );
results.push( {
type: 'comment',
id: comment.id,
context: ranges,
isResolved: comment.isResolved,
isUnlinked: !!comment.unlinkedAt
} );
} else if ( annotation.type.startsWith( 'suggestion' ) ) {
const suggestion = trackChangesUI.getSuggestionForAnnotation( annotation );
const ranges = [];
for ( const adjacentSuggestion of suggestion.getAllAdjacentSuggestions() ) {
ranges.push( ...adjacentSuggestion.getRanges() );
}
results.push( {
type: 'suggestion',
id: suggestion.id,
label: annotation.innerView.description,
context: ranges
} );
}
}
return JSON.stringify( results );Use the {@link features/revision-history revision history} feature API to build more functional integration between your application and the document revisions data.
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 just to 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.
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.
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. It is especially recommended for complex operations that would be cumbersome to implement directly in the server-side script.
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 {@link framework/architecture/plugins Plugins architecture} guide and the {@link tutorials/creating-simple-plugin-timestamp Creating a basic plugin} tutorial.