How to use CKEditor 5 in Laravel

In the past, I have written an article on using CKEditor 4 in Laravel. One of the readers asked me about integrating CKEditor 5 in Laravel. CKEditor 5 comes with more features as compared to CKEditor 4. You can read about the comparison on this page. In this article, we’ll study how to install and use CKEditor 5 in Laravel.

Why CKEditor?

While building a website, most of the time you wish to add rich content to the database. The rich content includes media, headings, text formatting, links, styles, etc. Adding such content requires to have a rich text editor(WYSIWYG editor). The WYSIWYG editors allow you to insert HTML as content which is otherwise not possible through the textarea tag.

There are a few popular WYSIWYG editors – CKEditor, TinyMCE, and Trix. This tutorial will be focused on CKEditor. For the integration of other editors please check out the linked articles.

In CKEditor, you will get options for adding rich content. This content you can grab on the server side and store in the database. To display the content on the front end, you just need to fetch it from the database and print it.

When it comes to adding rich content, it should also have the ability to insert images. This feature is not available by default in CKEditor 5. It requires you to add some extra code which I’ll show you in the later part of the tutorial. First, let’s see how to install CKEditor 5 in Laravel.

Installation of CKEditor 5

The process of installing CKEditor 5 is super easy. You can either download their package or use it via CDN.

The users can get the package on the CKEditor 5 download page. On this page, you’ll see different builds. However, I recommend using the CKEditor 5 Classic build.

download-ckeditor5

Extract the zip. You have to copy package files into your Laravel project. For this, create a ckeditor directory inside the public folder. Under this ckeditor directory, paste all files from the package. In my case, the ckeditor directory looks like the below screenshot.

ckeditor-files

If you choose to go with CDN, you don’t need to download the package. Instead, you can use the CDN link https://cdn.ckeditor.com/ckeditor5/37.0.1/classic/ckeditor.js into your Blade file.

How to Use CKEditor 5 in Laravel

Once you’re ready with the package files, include ckeditor.js in your blade component.

<script src="{{ asset('ckeditor/ckeditor.js') }}"></script>

For CDN, you will include it as follows.

<script src="https://cdn.ckeditor.com/ckeditor5/37.0.1/classic/ckeditor.js"></script>

After this, create a simple form that has two fields – Text Area and Submit button.

<form action="{{ url('ROUTE_HERE') }}" method="post">
    @csrf
    <textarea class="form-control" id="editor" name="editor"></textarea>
    <input type="submit" name="submit" value="Submit" />
</form>

Here, we’ll convert this Text Area into the CKEditor. To convert it write the below JavaScript.

<script>
ClassicEditor
    .create( document.querySelector( '#editor' ) )
    .catch( error => {
        console.error( error );
    } );
</script>

Now reload the page and you should see the CKEditor instead of a Text Area.

render-ckeditor5

Add the rich content into the editor and submit it. On the server side, you’ll get the content using the below statement.

$content = $request->input('editor');

To pre-populate CKEditor with content, add value to Text Area.

<textarea class="form-control" id="editor" name="editor">Your content here</textarea>

Easy enough! Now you can take benefit of all the basic features of CKEditor 5 except the images. In the next section, we’ll study how to allow uploading images in the CKEditor 5.

Upload Image in CKEditor 5

CKEditor 5 does not allow you to upload images directly. We can enable this feature by building a custom image upload adapter.

To build the custom adapter, we need to write both JavaScript and server-side code which then allows uploading images. While storing images, you should create a thumbnail with different sizes. This is because high-resolution images will not fit nicely inside the CKEditor. Having multiple thumbnails makes the images responsive in the editor.

In order to achieve this, we are going to perform the following steps.

  • Integrate Custom Image Uploader.
  • Store the images inside the public directory.
  • Resize images using the Intervention image library.

Add Custom Image Uploader

In the previous section, we write a basic code to call CKEditor in place of Text Area. Now to give an image upload feature, you need to replace this JavaScript code with the following one.

<script>
class MyUploadAdapter {
    constructor( loader ) {
        this.loader = loader;
    }

    upload() {
        return this.loader.file
            .then( file => new Promise( ( resolve, reject ) => {
                this._initRequest();
                this._initListeners( resolve, reject, file );
                this._sendRequest( file );
            } ) );
    }

    abort() {
        if ( this.xhr ) {
            this.xhr.abort();
        }
    }

    _initRequest() {
        const xhr = this.xhr = new XMLHttpRequest();

        xhr.open( 'POST', "{{route('upload', ['_token' => csrf_token() ])}}", true );
        xhr.responseType = 'json';
    }

    _initListeners( resolve, reject, file ) {
        const xhr = this.xhr;
        const loader = this.loader;
        const genericErrorText = `Couldn't upload file: ${ file.name }.`;

        xhr.addEventListener( 'error', () => reject( genericErrorText ) );
        xhr.addEventListener( 'abort', () => reject() );
        xhr.addEventListener( 'load', () => {
            const response = xhr.response;

            if ( !response || response.error ) {
                return reject( response && response.error ? response.error.message : genericErrorText );
            }

            resolve( response );
        } );

        if ( xhr.upload ) {
            xhr.upload.addEventListener( 'progress', evt => {
                if ( evt.lengthComputable ) {
                    loader.uploadTotal = evt.total;
                    loader.uploaded = evt.loaded;
                }
            } );
        }
    }

    _sendRequest( file ) {
        const data = new FormData();

        data.append( 'upload', file );

        this.xhr.send( data );
    }
}

function MyCustomUploadAdapterPlugin( editor ) {
    editor.plugins.get( 'FileRepository' ).createUploadAdapter = ( loader ) => {
        return new MyUploadAdapter( loader );
    };
}

ClassicEditor
    .create( document.querySelector( '#editor' ), {
        extraPlugins: [ MyCustomUploadAdapterPlugin ],
    } )
    .catch( error => {
        console.error( error );
    } );
</script>

If you notice, I have used the upload route in the above code. When we browse the image, CKEditor sends the image to this route for server-side processing. Let’s define this route as follows.

Route::post('editor/image_upload', 'EditorController@upload')->name('upload');

Next, create the EditorController using the command:

php artisan make:controller EditorController

As we need to upload an image on the server, create a symbolic link to the storage folder.

php artisan storage:link

Upload Image On A Server Side

As I mentioned, while storing images we should also create a thumbnail. For example, I am going to create a thumbnail with a width of 500px. It means we will store 2 images on the server. Both these images will be sent in response to CKEditor.

For creating a thumbnail of the image install the Intervention Image library through the command:

composer require intervention/image

Upon installation, open config/app.php and add a service provider to the $providers array.

Intervention\Image\ImageServiceProvider::class

Similarly, add the facade to the $aliases array.

'Image' => Intervention\Image\Facades\Image::class

Next, add this Image facade to the EditorController as follows.

<?php
 
namespace App\Http\Controllers;
 
use Illuminate\Http\Request;
use Image;
 
class EditorController extends Controller
{
 
}

Finally, in the upload method of the EditorController, we write a code that will upload a file on the server, create a thumbnail and send both images in the response.

public function upload(Request $request)
{
    if($request->hasFile('upload')) {
        //get filename with extension
        $filenamewithextension = $request->file('upload')->getClientOriginalName();
 
        //get filename without extension
        $filename = pathinfo($filenamewithextension, PATHINFO_FILENAME);
 
        //get file extension
        $extension = $request->file('upload')->getClientOriginalExtension();
 
        //filename to store
        $filenametostore = $filename.'_'.time().'.'.$extension;
 
        //Upload File
        $request->file('upload')->storeAs('public/uploads', $filenametostore);
        $request->file('upload')->storeAs('public/uploads/thumbnail', $filenametostore);

        //Resize image here
        $thumbnailpath = public_path('storage/uploads/thumbnail/'.$filenametostore);
        $img = Image::make($thumbnailpath)->resize(500, 150, function($constraint) {
            $constraint->aspectRatio();
        });
        $img->save($thumbnailpath);

        echo json_encode([
            'default' => asset('storage/uploads/'.$filenametostore),
            '500' => asset('storage/uploads/thumbnail/'.$filenametostore)
        ]);
    }
}

I am storing the original image inside the storage/uploads and thumbnail image into the storage/uploads/thumbnail directory.

CKEditor would then receive the response and add both images through src and srcset attributes in the img tag.

It’s all about using CKEditor 5 in Laravel. You should now easily take benefit of it in your Laravel application. Give it a try and let me know your thoughts and suggestions in the comment section below.

Related Articles

If you liked this article, then please subscribe to our YouTube Channel for video tutorials.

Leave a Reply

Your email address will not be published. Required fields are marked *