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).
Table Of Content
1 Prerequisites
- Laravel 11.x
- PHP 8.2+
- Composer
- MySQL database
2 Introduction
3 Install Laravel Project
3.1 Install Laravel Project
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
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
<script src="https://cdn.ckeditor.com/ckeditor5/41.4.2/classic/ckeditor.js"></script>
5 Create Migration and Model
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
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
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
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
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
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.
