Using CKEditor in Laravel Project

Video thumbnail

Content Index

When I started working with WYSIWYG editors in Laravel, I tried many. Lightweight plugins, modern alternatives, even some visually very nice ones... but in the end, I always returned to CKEditor. For me, it is “the Word for the web”: straightforward, complete, and capable of showing exactly what the user is going to see. And since I’ve been working with CKEditor since version 3, I can say that the jump to CKEditor 5—which is the one we’re going to integrate here—is totally worth it.

This tutorial is not just another “copy and paste installation.” Here I tell you how I use it in my real projects, the errors that will almost always come up, how to react when CKEditor decides to show you the famous filerepository-no-upload-adapter, and how to prepare your custom build with Vite in Laravel.

CKEditor is ideal for my dashboard, for managing this type of publication, whose module I protect using the Spatie Permissions system in Laravel.

What is CKEditor 5 and why it is ideal for Laravel projects

If you’ve never worked with WYSIWYG editors, CKEditor 5 is literally what you see-is-what-you-get. I call it that because it behaves like an “online Word”: you can add titles, tables, code blocks, colors, strikethroughs, whatever you want... and you see it exactly as it will appear on your blog, panel, or administrative section.

In my case, I use it a lot for publishing projects, especially blogs in Laravel. When I open the details of a post, I don’t want to see a plain, sad text: I want it to have life, formatting, and, if necessary, video embeds or well-highlighted code blocks.

CKEditor: the Word for the web

Maybe this is the first time you’ve heard about this, or maybe not, I don't know. But basically, CKEditor is exactly what its name says: what you see is what you get. I call it an “online Word” or a “Word for the web,” as it offers features similar to Microsoft Word or Google Docs.

I mean an editor that allows you to apply formats like bold, italics, strikethrough text, color change, titles, tables, lists, etc. This is precisely what the term What You See Is What You Get (abbreviated as WYSIWYG) means: you can format your text and see in real-time how it will look.

That is, we are talking about a plugin that allows us to enrich the content, so that it doesn't look so plain or boring, but with a more visually attractive style. This is ideal, of course, when we work with a blog.

Applying it to our blog

That is why I use this Laravel project again, since we are supposed to be working on a blog and we don’t want our publications to appear so simple.

When you enter the details of a publication, it shouldn't look so "pale," but should show enriched content. For example, you can insert YouTube videos, something more advanced than the basic configurations that these editors bring by default.

In this type of editor, like the one you see above, you don't have options to insert a YouTube video or anything similar. Only basic things like titles, bold text, or underlined text.

In addition, I also place code blocks in my publications. That format is defined elsewhere, but when we use CKEditor, we can directly indicate that a block corresponds to code, among many other options.

Video thumbnail

Now, since we know what these acronyms mean—What You See Is What You Get—which can be referenced in various ways, in a nutshell it's a way to define enriched content.
I already showed you an example of enriched content both in my book and on my blog, so I think the time has come to present some available plugins for this.

Searching for a WYSIWYG editor

Obviously, if you type "What You See Is What You Get JavaScript" into Google, you'll find a lot of plugins. Note: I'm not saying they are bad or anything like that. Surely many of them are excellent, and have been on the market for quite some time.

Why do I use CKEditor?

In my case, I use CKEditor for a very simple reason: it is a project that, colloquially speaking, seems serious to me.
It has been on the market for years, I have been using it for a long time—I think since version 3, although not that long ago—and the team behind the plugin constantly keeps it updated.

This is important, because not all projects have that consistency in their improvements.
Furthermore, CKEditor is highly customizable, and has good documentation (although sometimes it may seem a little abstract).

What makes it stand out? What makes CKEditor stand out?

  • It is available for Vanilla JavaScript, which is what we are going to use (without additional frameworks).
  • You can also integrate it with frameworks like Angular, React, Vue 2, Vue 3, and even with .NET.
  • It has extensive documentation, and an active team that keeps the project alive.
  • There is a demos section, where you can see what it looks like, how it works, and test many of its functionalities.
  • The editor even includes an AI assistant, which demonstrates the constant evolution of the product.
  • You can export to PDF, Word, modify the interface, etc.
  • It is visually beautiful and functionally very complete.

Advantages over other WYSIWYG editors

I have tried many others. Some are good, others very simple. But CKEditor stands out for:

  • Stability (years on the market).
  • An active team that constantly updates.
  • Advanced features like exporting to PDF, Word, premium plugins, CKBox, etc.
  • The possibility of creating your own build by just checking boxes.
  • Clean integration with modern frameworks.

Why I use it for real projects in Laravel

I use it because:

  • It has worked well for me for years.
  • I know it won't fall short if I need more features tomorrow.
  • The build is highly customizable.
  • Although the documentation can be somewhat “dense,” once you understand the flow, everything fits together.

And the price?

Some of its more advanced features are paid. That is something to take into account.
For example, the product called SunBox (I think that’s the name), which you can integrate even with services like Dropbox and other online drives, is a separate solution that has a cost.

However, even with the limitations of the free version, the plugin already offers a lot.

Why do I use it?

In short, I use CKEditor for three reasons:

  1. Trust: I have been using it for years and it has always responded well.
  2. Customization: I can adjust it to practically whatever I want.
  3. Scalability: I know it will never fall short if I need to expand functionalities.

Yes, it is true that its configuration can be a bit complex. In fact, that's why I'm making these videos: to help you overcome that initial barrier.

Prerequisites and environment preparation

Installation via NPM

Laravel already comes with Node integration, so this step is straightforward:

$ npm i ckeditor5

Initial file structure (JS, CSS and build)

I usually create:

resources/js/ckeditor.js resources/css/ckeditor.css

And in ckeditor.js I import the build modules:

// My personal comments are always with double slash (//)
// That way I know what to delete and what to keep later. import Editor from './build/editor.js';
import './build/styles.css';

Generate the custom build

Video thumbnail

From the following website:

https://ckeditor.com/ckeditor-5/builder/

You can generate your custom CKEditor, ready to copy and paste and replace or complement code in editor.js.

Configure CKEditor 5 in a Laravel project with Vite Configuring CKEditor 5 in a Laravel project with Vite

Here comes one of the parts that confuses beginners the most.

Settings in vite.config.js

Laravel needs to know which JS and CSS files it should process. Therefore, I add the editor:

export default defineConfig({
   plugins: [
       laravel({
           input: [
               'resources/css/ckeditor.css',
               'resources/js/ckeditor.js',
           ],
           refresh: true,
       }),
   ],
});

In the case of Vite, it is important to take the configuration into account. I think I already explained it before, but basically, when we use Vite—which in this case functions as a “compiler,” so to speak—what it does is interpret our transpilation. That is, it translates our CSS or JavaScript into a format that the browser can understand.

To clarify the concept: a traditional compiler would be, for example, Java, which takes a .java file and compiles it into binary code. In the case of Vite, the same type of code is maintained, but it is transformed, for example, from modern JavaScript to a version compatible with old browsers, or from SCSS to CSS.

Within the Vite configuration, we define the files we want it to interpret and we also indicate the corresponding path, just as we established previously.

Integrate CKEditor 5 in a Blade form

You can use a textarea:

<textarea class="form-control !hidden content" name="content"> {{ old('content', $post->content) }} </textarea>

Or a div:

<div id="editor"> {!! old('content', $post->content) !!} </div>

The <div> is what CKEditor will transform into a visual editor; the textarea remains hidden to save the data when submitting the form.

And at the end:

*** @vite(['resources/css/ckeditor.css', 'resources/js/ckeditor.js'])

Editor initialization step by step

This code is the crucial part of the editor and is already configured in the previously generated build:

   .create(document.querySelector('#editor'))
   .then(editor => {
      // Don't be afraid: here you can customize
       window.editor = editor;
   })
   .catch(error => console.error(error));

How to enable image uploading (SimpleUploadAdapter) How to enable image uploading (SimpleUploadAdapter)

Video thumbnail

Most people get stuck because they drag an image, CKEditor tries to process it... and the error appears:

ckeditor5.js?v=efbb40b8:9479 filerepository-no-upload-adapter Read more: https://ckeditor.com/docs/ckeditor5/latest/support/error-codes.html#error-filerepository-no-upload-adapter

It happened to me a thousand times until I understood that CKEditor doesn't upload anything if you don't specify what adapter you are going to use.

Configure the adapter on the client side

Example:

import { SimpleUploadAdapter } from 'ckeditor5';
const editorConfig = {
   plugins: [ SimpleUploadAdapter ],
   simpleUpload: {
       uploadUrl: '/dashboard/post/upload/ckeditor',
       headers: {
           'X-CSRF-TOKEN': document
               .querySelector('meta[name="csrf-token"]')
               .content
       }
   }
};

Without this, CKEditor doesn't know where to send the image:

uploadUrl : '/dashboard/post/upload/ckeditor',	

So we go back here. We are going to import it anywhere. Remember that it is the import, so it doesn't matter where you place it; it can go up.

Below are the simple options. Here is an adapter; be careful with the name, since we reference it with this name in the plugins section. You can find it up here, I'm going to put it and, with this, we can use the option called simple.

After all this, the order doesn't matter; the important thing is that it is after the plugin. All these are the options, just as I explained before, a little when I commented on the structure we have in all this.

Avoid 419 errors by correctly configuring CSRF

Remember that in Laravel we have CSRF protection, so we also have to configure it and we usually have it in a meta tag:

<meta name="csrf-token" content="{{ csrf_token() }}">

Now, to configure the headers, we go to the headers section and place the following:

	simpleUpload:{
		uploadUrl : '/dashboard/post/upload/ckeditor',	
		headers: {
			'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
		}
	},

Upload route and controller in Laravel

Route::post('/dashboard/post/upload/ckeditor', [PostController::class, 'upload']);

And in the controller:

public function upload(Request $request)
{
   $file = $request->file('upload');
   $path = $file->store('uploads', 'public');
   return response()->json([
       'url' => asset('storage/'.$path)
   ]);
}

Recommended plugins and advanced editor options Recommended plugins and advanced editor options

Free and widely used plugins:

  • Essentials
  • Paragraph
  • Bold
  • Italic
  • Table
  • CodeBlock
  • Image
  • ImageUpload

Premium plugins: CKFinder, CKBox, etc.

Video thumbnail

When I tested CKBox, I noticed that it is excellent, but paid. For most small or medium projects, SimpleUploadAdapter is more than enough.

Ways to Configure Upload in CKEditor

We can work with two main ways: the Premium way, using a couple of packages, and the “poor man's way,” which is simpler and uses these two options: the SimpleUploadAdapter, which already comes as a little package.

import {
    ClassicEditor,
    SimpleUploadAdapter,
    ***
} from 'ckeditor5';

In this way we can import everything. Later we will see how to import it, configure it, and adjust some details.

The other option is to create a custom adapter. The important thing is that you notice the word adapter, which is exactly what is indicated here.

Regarding which one to use: I would go for the SimpleUploadAdapter, because it is simpler. I used to use the other option in CKEditor 3 or 4, but in version 5 I didn't need it and, in fact, when trying the code I had, it didn't work correctly; surely it was some small detail that needed to be corrected.

In any case, this first option is sufficient for most cases. We simply treat it somewhat theoretically now, and then we will implement it in practice:

https://ckeditor.com/docs/ckeditor5/latest/features/images/image-upload/image-upload.html

Embed Responsive Youtube Videos on your website + Laravel CKEditor Embed Responsive Youtube Videos on your website + Laravel CKEditor

Video thumbnail

Another detail that I consider interesting is that adaptive or responsive YouTube videos can be embedded on our website.

If we go to YouTube, select the video, click on "Share," then "Embed," and copy the generated code.

If you try to paste that code directly into the editor, you will see that it does not automatically adapt to the window size. To solve this, we have a couple of ways, and this again depends on how you have configured your editor.

Video Responsiveness Video Responsiveness

The problem? The video is not responsive, that is, it does not adapt to different screen sizes. You will see that a very uncomfortable scroll bar appears.

To solve this, we are going to adapt the video manually, we define the following CSS:

.video-container {
	aspect-ratio: 16 / 9;
	margin: 15px auto;
	overflow: hidden;
	position: relative;
	width: 100%;
	max-width: 600px;
}

.video-container iframe {
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
}

And now we define the videos as:

<div class="video-container"> <iframe src="..." frameborder="0" allowfullscreen></iframe> </div>

With this, the iframe has width and height at 100%, top and left at 0, and the defined aspect ratio is handled from the container.

You can play with these values ​​to see how it behaves. This HTML and CSS works for any YouTube video with a 16:9 aspect ratio, which is the most common.

Installing and Configuring CKEditor in Laravel Livewire

Video thumbnail

In Laravel Livewire, the installation process is exactly the same; the only difference is that we use it in a Laravel Livewire component, which is simply a DIV, along with a higher-level DIV for column-based alignment.

resources/views/livewire/dashboard/post/save.blade.php

***
<div class="col-span-10 sm:col-span-6">
    <Label for="">Text</Label>
    <div id="ckcontent">{!! $text !!}</div>
</div>
***

At the end of the component, we add the CKEditor script and to create it:

***
    </x-form-section>
    <script src="{{ asset('js/ckeditor/ckeditor.js') }}"></script>
        @vite(['resources/js/ckeditor.js'])
    </div>
</div>

Although in recent versions of Laravel Livewire there shouldn't be any problems updating the component via Livewire when writing to an HTML element wire:model, if you need to perform an update before submitting the form (that is, when in the developer console, in the network section, we see requests, and the affected component is redrawn, which in this example would be the save component), you can specify the following attribute:

<div wire:ignore class="col-span-6 sm:col-span-4">
   <div id="ckcontent">{!! $text !!}</div>
</div>

Remove characters and HTML from CKEditor

Video thumbnail

I wanted to quickly show you a CKEditor implementation I'm working on. As I mentioned, I have a bit of a love-hate relationship with CKEditor: it's an excellent plugin with many options, but some configurations can be a bit complicated. For example, certain options aren't easy to remove.

The feature I'm implementing is on my Academia website. I was somewhat inspired by the people at PacktPub, which allows you to view books directly on the website. For example, when you click on a book, you can see the HTML directly, which I think is excellent.

Problem with the generated HTML content

The problem I'm having is that the HTML generated by CKEditor automatically adds unnecessary <br> tags when copied. The original content I'm copying doesn't have <br> tags or anything unusual, just some basic classes and tags.

To illustrate, I take a piece of content (in this example, not a whole chapter) and copy it from Source into CKEditor. Then I save it.

Upon review, it looks like this:

  • Some line breaks appear "dead".
  • CKEditor automatically adds <br>, even where they shouldn't be.
  • This complicates direct manipulation of the HTML.

My implementation for removing unwanted HTML

I'm not entirely proud of this solution, but it's what I came up with to avoid messing around too much with the CKEditor settings.

Instead of directly submitting the content with wire:submit.prevent, I do the following.

I create a regular form with an ID:

<form id="formData">
  {{-- <form wire:submit.prevent="sendData"> --}}
</form>

I use JavaScript to listen for the submit event:

document.querySelector("#formData").addEventListener("submit", function(event) {
   event.preventDefault();
   // Código para manipular HTML y enviarlo
});

I store the editor's content in a hidden element where I can manipulate it:

<div id="htmlCkeditor" class="hidden"></div>
document.querySelector("#htmlCkeditor").innerHTML = editor.getData();
document.querySelector("#htmlCkeditor").innerHTML =
   document.querySelector("#htmlCkeditor").innerHTML.replaceAll('<p>&nbsp;</p>', "");

Finally, I send the clean content to the server using Livewire:

$wire.submit(document.querySelector("#htmlCkeditor").innerHTML);

Note: This part can be replaced with the technology you are using, such as Axios or Inertia, to make the request to the server.

Problem of modifying HTML directly in CKEditor

CKEditor automatically redefines the HTML and saves a sort of internal mirror. This prevents direct editing of the content using JavaScript within the editor.

At first, I spent over two hours trying to remove the <br> tags and couldn't understand why they kept regenerating. Finally, I discovered that by manipulating the content outside the editor before sending it to the server, I can remove the unwanted elements without any problems.

Installing and Configuring CKEditor in Laravel Inertia - Vue

Video thumbnail

In Laravel Inertia or Vue, the process changes because, since you're configuring Vue instead of a Blade template, the steps are slightly different.

When using Inertia with Vue as the view engine, you need to integrate CKEditor for Vue. One of the advantages of CKEditor is its compatibility with many JavaScript technologies, such as React, Angular, and WordPress, among others.

In the case of Inertia with Vue, you need to install two packages:

$ npm install --save @ckeditor/ckeditor5-vue @ckeditor/ckeditor5-build-classic

CKEditor is a Word-like text editor designed for web applications. It's an open-source, user-friendly, and highly recommended WYSIWYG plugin. It has been around for a long time and receives regular updates, making it a reliable choice for web projects.

Configuration in Vue

To integrate CKEditor into Vue within a Laravel project using Inertia, add the following to the resources/js/app.js file:

import CKEditor from '@ckeditor/ckeditor5-vue';
createInertiaApp({
   title: (title) => `${title} - ${appName}`,
   resolve: (name) => require(`./Pages/${name}.vue`),
   setup({ el, app, props, plugin }) {
       return createApp({ render: () => h(app, props) })
           .use(plugin)
           .use(Oruga) // otros plugins como Oruga UI
           .use(CKEditor)
           .mixin({ methods: { route } })
           .mount(el);
   },
});

Use in Vue components

Let's say we have a textarea field in a posts form. We convert it into a WYSIWYG editor using CKEditor:

<template>
 <div class="col-span-6">
     <jet-label value="Text" />
     <ckeditor :editor="editor.editor" v-model="form.text"></ckeditor>
     <jet-input-error :message="errors.text" />
 </div>
</template>
<script>
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";
export default {
 components: {
   ClassicEditor
 },
 data() {
   return {
     editor: {
       editor: ClassicEditor
     }
   };
 },
};
</script>

We imported the Classic Editor:

import ClassicEditor from "@ckeditor/ckeditor5-build-classic";

We created the <ckeditor> component and associated the v-model with our form field:

<ckeditor :editor="editor.editor" v-model="form.text"></ckeditor>

With this, any changes made in the editor will be reflected in form.text, ready to be sent to the server and saved in the database.

CKEditor
CKEditor

CSS with Tailwind

When using Tailwind in the application, we lose the default styling, such as the sizes of titles, lists, etc. Therefore, to recover this styling, we define a CSS like the following:

resources/css/app.css

/* CKEDITOR */
.ck-editor__editable_inline {
    min-height: 400px;
}

.ck-editor__main h1 {
    font-size: 40px;
}

.ck-editor__main h2 {
    font-size: 30px;
}

.ck-editor__main h3 {
    font-size: 25px;
}

.ck-editor__main h4 {
    font-size: 20px;
}

.ck-editor__main ul {
    list-style-type: circle;
    margin: 10px;
    padding: 10px;
}

.ck-editor__main ol {
    list-style-type: decimal;
    margin: 10px;
    padding: 10px;
}
/* CKEDITOR */

Final conclusions

Integrating CKEditor 5 in Laravel is simpler than it seems when you understand four things:

  1. Vite must compile the editor's JS and CSS.
  2. You must correctly initialize the editor in Blade.
  3. You must configure an upload adapter (SimpleUploadAdapter) to avoid errors.
  4. Generate your custom build from the CKEditor website.

I use it in real projects, it is a great plugin that I have used for many years, especially for blogs, and I can tell you that it is worth dedicating a few minutes to configure it. Once you have it ready, it is stable, elegant, and tremendously useful.

Frequently asked questions about CKEditor 5 in Laravel Frequently asked questions about CKEditor 5 in Laravel

  • How to correctly install CKEditor 5?
    • With NPM: npm i ckeditor5, create JS/CSS files and configure them in Vite.
  • Why does CKEditor show a 419 error?
    • CSRF failure. Make sure to read the token from the <meta> and send it in headers.
  • How to activate drag and drop images?
    • Include SimpleUploadAdapter and define your uploadUrl.
  • What plugins are essential for a blog?
    • Bold, Italic, Headings, CodeBlock, Image, ImageUpload, Table.

Next step, learn how to use the Laravel HTTP client.

I agree to receive announcements of interest about this Blog.

We are going to embed the CKEditor as part of the form so that the post content is now rich/html.

| 👤 Andrés Cruz

🇪🇸 En español