Why Use CKEditor 5 in Laravel?

             CKEditor 5 is a modern WYSIWYG editor for rich content. This tutorial shows a simple custom image upload (via CKEditor's ckfinder adapter) — no paid CKFinder required. For advanced file management, consider the official CKFinder Laravel package (commercial).

How to Integrate CKEditor 5 with Image Upload in Laravel 11

Table Of Content

1 Prerequisites

  • Laravel 11.x
  • PHP 8.2+
  • Composer
  • MySQL database

2 Introduction

While we are developing Applications on Laravel 11 framework sometimes we need rich text area fields. In these kind of cases, CKEditor is one of the best option for us. This tutorial will guide you through the process of integrating CKEditor with CKFinder for image upload to the server in Laravel App.

3 Install Laravel Project

3.1 Install Laravel Project

First, make sure your computer has a composer.
Use the following command to install new Laravel Project.

composer create-project laravel/laravel ckeditor-app

Then, navigate to your project directory:

cd ckeditor-app

3.2 Configure MySql Database

Upon Successful Transaction, the payment trasactions data will be stored in the database. This process involves accessing the .env file to input and define the database credentials.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=ckeditor_db
DB_USERNAME=root
DB_PASSWORD=

4 Install CKEditor 5

In your resources/views/post.blade.php file, include CKEditor 5's CDN link within the head section:

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

5 Create Migration and Model

Now, you need to create migration for new table "post" to store the Post data. Also, create a model Payment for the same. Run this below command.

php artisan make:model Post —migration

In the generated new migration file, update the up and down methods as described below:

database/migrations/2024_05_31_014545_create_post_table.php


<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->longText('content');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        //
    }
};

Use the following command to run the migration to update your database.

php artisan migrate

app/Models/Post.php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use HasFactory;
   protected $fillable = ['title', 'content'];
}


6 Create New Controller - CkeditorController

Now create a controller "CkeditorController" and add index() and store() methods, also Create "media" folder under Public Directory to store Images
Use the following artisan command to Create Controller.

php artisan make:controller CkeditorController

app/Http/Controllers/CkeditorController.php

<?php
   
namespace App\Http\Controllers;
    
use Illuminate\Http\Request;
use Illuminate\View\View;
use Illuminate\Http\JsonResponse;
use App\Models\Post;

   
class CkeditorController extends Controller
{
    public function index(): View
    {
        return view('post');
    }
    
    
    public function upload(Request $request): JsonResponse
    {
        if ($request->hasFile('upload')) {
            $originName = $request->file('upload')->getClientOriginalName();
            $fileName = pathinfo($originName, PATHINFO_FILENAME);
            $extension = $request->file('upload')->getClientOriginalExtension();
            $fileName = $fileName . '_' . time() . '.' . $extension;
      
            $request->file('upload')->move(public_path('media'), $fileName);
      
            $url = asset('media/' . $fileName);
  
            return response()->json(['fileName' => $fileName, 'uploaded'=> 1, 'url' => $url]);
        }
    }
    public function store(Request $request)
    {
        $post=new Post;

        $post->title=$request->title;

         $post->description=$request->description;

         $post->save();

         return redirect()->back()->with('message',"Post Created Successful");

    }
}
?>

7 Define a Route

Define routes for the CkeditorController in the web.php file
routes/web.php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\CkeditorController;

Route::get('ckeditor', [CkeditorController::class, 'index']);
Route::post('ckeditor/upload', [CkeditorController::class, 'upload'])->name('ckeditor.upload');
Route::post('/store',[CkeditorController::class,'store'])->name('ckeditor.store');


8 Create Post View File

Create View "post.blade.php" File to Show Form
resources/views/post.blade.php


  <!DOCTYPE html>
  <html lang="en">
   <head>
     <meta charset="utf-8">
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="viewport" content="width=device-width, initial-scale=1">
     <title>Laravel 11 Ckeditor Image Upload Example with CKFinder</title>
     <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
     <link rel="stylesheet" href="https://mdbcdn.b-cdn.net/wp-content/themes/mdbootstrap4/docs-app/css/dist/mdb5/standard/core.min.css">
     <link rel='stylesheet' id='roboto-subset.css-css'  href='https://mdbcdn.b-cdn.net/wp-content/themes/mdbootstrap4/docs-app/css/mdb5/fonts/roboto-subset.css?ver=3.9.0-update.5' type='text/css' media='all' />
     <script src="https://cdn.ckeditor.com/ckeditor5/41.4.2/classic/ckeditor.js"></script>
     <style>
      .ck-editor__editable_inline {
          min-height: 300px;
      }
      </style>
  </head>
   <body>
   <div class="container-fluid">
  
   @if(session()->has('error'))
      <div class="alert alert-danger">
          {{ session()->get('error') }}
      </div>
  @endif
  
  @if(session()->has('message'))
      <div class="alert alert-success">
          {{ session()->get('message') }}
      </div>
  @endif
    <div class="row">
    <div class="col-md-3"></div>
      <div class="col-md-6 mb-4">
        <div class="card mb-4">
          <div class="card-header py-3">
            <h5 class="mb-0">Laravel 11 Ckeditor Image Upload Example with CKFinder</h5>
          </div>
          <div class="card-body">
          <form class="form-horizontal" method="POST" id="post-form" role="form" action="{!!route('ckeditor.store')!!}" >
            {{ csrf_field() }}
              <div class="row mb-4">
                <div class="col">
                  <div data-mdb-input-init class="form-outline">
                    <input type="text" id="name" class="form-control" name="title" />
                    <label class="form-label" for="name">Title</label>
                  </div>
                </div>
                
              </div>
  
              
              <div class="row">
                <div class="row mb-4">
                <div class="col-12">
                  <div data-mdb-input-init class="form-outline">
                  <label class="form-label" for="name">Description</label> 
                  <textarea name="description" id="editor" rows="5"></textarea>
                   
                  </div>
                </div>
                
              </div>
                
              <hr class="my-4" />
  
             
              <button class="btn btn-primary btn-lg btn-block" type="submit">
                Save
              </button>
            </form>
          </div>
        </div>
      </div>
      <div class="col-md-3"></div>
      
    </div>
    </div>
    <script type="text/javascript" src="https://mdbcdn.b-cdn.net/wp-content/themes/mdbootstrap4/docs-app/js/dist/mdb5/standard/core.min.js"></script>
      
  
      
      <script type="text/javascript" src="https://mdbcdn.b-cdn.net/wp-content/themes/mdbootstrap4/docs-app/js/dist/search/search.min.js"></script>
      
  <script>
      ClassicEditor
          .create( document.querySelector( '#editor' ),{
              ckfinder: {
                  uploadUrl: '{{route('ckeditor.upload').'?_token='.csrf_token()}}',
              }
          })
          .catch( error => {
                
          } );
  </script>
  
  </body>
  </html>

9 Folder Structure

10 Run Laravel Server to Test the App

Use the following artisan command to Test the App.

php artisan serve

Visit: http://127.0.0.1:8000/ckeditor

Best Practices & Enhancements

  • Use Laravel Storage (e.g., Storage::disk('public')) instead of public_path().
  • Add image optimization (Intervention/Image package).
  • Validate file size/type.
  • For full file manager → Install official CKFinder Laravel package (commercial).

11 Conclusion

This setup gives you a clean, functional CKEditor 5 with image uploads in Laravel 11. It's lightweight and customizable. For advanced needs (browsing, editing), explore CKBox or full CKFinder.
Revathi M - PHP and CodeIgniter Developer

Written by Revathi M

PHP Developer & Technical Writer · 10+ years building web applications with CodeIgniter and Laravel

Revathi specializes in PHP backend development, authentication systems, and REST API design. She writes practical, production-tested tutorials at Get Sample Code to help developers build secure applications faster.

Frequently Asked Questions

You need PHP 8.2 or higher, Composer, and a MySQL database.

Run `composer create-project laravel/laravel project-name`, then `cd project-name`.

Update the `.env` file with your MySQL details: `DB_DATABASE`, `DB_USERNAME`, `DB_PASSWORD`, etc.

Add the CDN script: <script src='https://cdn.ckeditor.com/ckeditor5/41.4.2/classic/ckeditor.js'> </script>`.

Use `ClassicEditor.create(document.querySelector('#editor'), { ckfinder: { uploadUrl: '{{route('ckeditor.upload')}}?_token={{csrf_token()}}' } })`.

Run `php artisan make:controller CkeditorController`, then add `index()`, `upload()`, and `store()` methods.

In the `upload()` method, check for `upload` file, generate a unique filename with timestamp, move it to `public/media`, and return JSON with `uploaded: 1` and `url`.

Images are saved in the `public/media` directory.

Ensure the `public/media` folder exists and has write permissions (e.g., 777). Also, verify the CSRF token is correctly appended to the upload URL.

In `routes/web.php`, add `Route::get('ckeditor', [CkeditorController::class, 'index']);`, `Route::post('ckeditor/upload', ...)->name('ckeditor.upload');`, and a store route.

Create a `Post` model and migration for the `posts` table, then in `store()`, insert the title and description (including HTML with image tags).

The tutorial uses CKEditor 5's `ckfinder` option with a custom Laravel upload route, not the separate CKFinder package.

Manually create the `media` folder inside the `public` directory.

Run `php artisan serve`, then visit `http://127.0.0.1:8000/ckeditor` to test editing, uploading images, and submitting the form.