In this guide you will learn the basic concepts of the file upload architecture in CKEditor 5 WYSIWYG editor which will help you implement your own custom upload adapter.
While this guide is mainly focused on the image upload (the most common kind of upload), keep in mind that the presented concepts and the API allow developing all sorts of file upload adapters for different file types like PDFs, movies, etc.
If you do not feel like getting through this guide but you want a simple upload adapter that works, check out the Simple upload adapter plugin we implemented for you.
Check out the comprehensive Image upload overview to learn about other ways to upload images into CKEditor 5.
Glossary of terms Before we start, let’s make sure all terms used in this guide are clear.
Term Description Upload adapter A piece of code (a class) that handles the image upload from the moment it is requested by the user (e.g. when the file is dropped into the content) to the moment the server returns a response to the requested upload. A bridge between the feature and the server.
Upload adapters are used by other plugins like image upload to connect to the server and fetch the response. For every user action (e.g. when a file is dropped into the content), a new upload adapter instance is created.
CKEditor 5 comes with some official upload adapters but you can also implement your own adapters.
See the "How does the image upload work?" section to learn more.
UploadAdapter interface An interface defining the minimal API required to create an upload adapter. In other words, it tells you what methods your upload adapter class must have in order to work.
See "The anatomy of the adapter" section to learn more.
File repository plugin A central point for managing file upload in CKEditor 5. It glues upload adapters and features using them:
Upload adapters are enabled in the editor by defining the FileRepository.createUploadAdapter() factory method. Features like image upload use the FileRepository API to create a new upload adapter instance each time an upload is requested by the user. Image upload plugin A top–level plugin that responds to actions of the users (e.g. when a file is dropped into the content) by uploading files to the server and updating the edited content once the upload finishes. This particular plugin handles user actions related to uploading images.
It uses the FileRepository API to spawn upload adapter instances, triggers the image upload (UploadAdapter.upload()) and finally uses the data returned by the adapter's upload promise to update the image in the editor content.
See the "How does the image upload work?" section to learn more.
How does the image upload work? Before you can implement your own custom upload adapter, you should learn about the image upload process in CKEditor 5. The whole process boils down to the following steps:
First, an image (or images) need to get into the rich-text editor content. There are many ways to do that, for instance:
pasting an image from clipboard, dragging a file from the file system, selecting an image through a file system dialog. The images are intercepted by the image upload plugin (which is enabled in all official editor builds).
For every image, the image upload plugin creates an instance of a file loader.
The role of the file loader is to read the file from the disk and upload it to the server by using the upload adapter. The role of the upload adapter is, therefore, to securely send the file to the server and pass the response from the server (e.g. the URL to the saved file) back to the file loader (or handle an error, if there was one). While the images are being uploaded, the image upload plugin:
Creates placeholders of these images. Inserts them into the editor. Displays the progress bar for each of them. When an image is deleted from the editor content before the upload finishes, it aborts the upload process. Once the file is uploaded, the upload adapter notifies the editor about this fact by resolving its Promise. It passes the URL (or URLs in case of responsive images) to the image upload plugin which replaces the src and srcset attributes of the image placeholder in the editor content.
This is just an overview of the image upload process. Actually, the whole thing is more complicated. For instance, images can be copied and pasted within the WYSIWYG editor (while the upload takes place) and all potential upload errors must be handled, too. The good news is these tasks are handled transparently by the image upload plugin so you do not have to worry about them.
To sum up, for the image upload to work in the rich-text editor, two conditions must be true:
The image upload plugin must be enabled in the editor. It is enabled by default in all official builds, but if you are customizing CKEditor 5, do not forget to include it.
The upload adapter needs to be defined. This can be done by using (enabling and configuring):
One of the existing upload adapters. Your custom upload adapter and handling uploaded files on your server back–end. The anatomy of the adapter A custom upload adapter allows you to take the full control over the process of sending the files to the server as well as passing the response from the server back to the rich-text editor.
Any upload adapter, whether an image upload adapter or a generic file upload adapter, must implement the UploadAdapter interface in order to work, i.e. it must bring its own upload() and abort() methods.
The upload() method must return a promise: resolved by a successful upload with an object containing information about the uploaded file (see the section about responsive images to learn more), rejected because of an error, in which case nothing is inserted into the content. The abort() method must allow the editor to abort the upload process. It is necessary, for instance, when the image was removed from the content by the user before the upload finished or the editor instance was destroyed. In its simplest form, a custom adapter implementing the UploadAdapter interface will look as follows. Note that server.upload(), server.onUploadProgress() and server.abortUpload() should be replaced by specific implementations (dedicated for your application) and only demonstrate the minimal communication necessary for the upload to work:
class MyUploadAdapter { constructor( loader ) { // The file loader instance to use during the upload. this.loader = loader; }
// Starts the upload process.
upload() { // Update the loader's progress. server.onUploadProgress( data => { loader.uploadTotal = data.total; loader.uploaded = data.uploaded; } ); // Return a promise that will be resolved when the file is uploaded. return loader.file .then( file => server.upload( file ) );
} // Aborts the upload process.
abort() { // Reject the promise returned from the upload() method. server.abortUpload();
}
}
Define the FileRepository.createUploadAdapter() factory meth