
Table Of Content
1 Prerequisites
- PHP ≥ 8.1
- Composer installed
- MySQL database server
- Basic terminal access
2 Introduction
3 Install CodeIgniter 4
3.1 Install Codeigniter 4 Project
composer create-project codeigniter4/appstarter ckeditor-ci4
Then, navigate to your project directory:
cd ckeditor-ci4
3.2 Configure MySql Database
database.default.hostname = localhost
database.default.database = ci4_ckeditor
database.default.username = root
database.default.password =
database.default.DBDriver = MySQLi
Create the database ci4_ckeditor in your MySQL server (e.g., via phpMyAdmin or command line: mysql -u root -p -e "CREATE DATABASE ci4_ckeditor;"). 4 Include CKEditor 5
<script src="https://cdn.ckeditor.com/ckeditor5/43.0.0/classic/ckeditor.js"></script>
5 Create Migration and Model
Create a migration file for the posts table:
php spark make:migration CreatePostsTable
Edit app/Database/Migrations/
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class CreatePostsTable extends Migration
{
public function up()
{
$this->forge->addField([
'id' => ['type' => 'BIGINT', 'auto_increment' => true],
'title' => ['type' => 'VARCHAR', 'constraint' => 255],
'content' => ['type' => 'LONGTEXT'],
'created_at' => ['type' => 'DATETIME', 'null' => true],
'updated_at' => ['type' => 'DATETIME', 'null' => true],
]);
$this->forge->addKey('id', true);
$this->forge->createTable('posts');
}
public function down()
{
$this->forge->dropTable('posts');
}
}
Run the migration:
php spark migrate
php spark make:model Post
Edit app/Models/Post.php to configure the model for post management:
<?php
namespace App\Models;
use CodeIgniter\Model;
class Post extends Model
{
protected $table = 'posts';
protected $allowedFields = ['title', 'content'];
protected $useTimestamps = true;
}
6 Create the Controller
php spark make:controller Ckeditor
Edit app/Controllers/Ckeditor.php:(this handles the form, image upload, and storage with security):
PHP
<?php
namespace App\Controllers;
use App\Models\Post;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Controller;
class Ckeditor extends Controller
{
protected $model;
public function __construct()
{
$this->model = new Post();
}
public function index()
{
return view('ckeditor_form');
}
public function upload(): ResponseInterface
{
// Validate CSRF (automatic in CI4 if enabled)
$file = $this->request->getFile('upload');
if (!$file || !$file->isValid()) {
return $this->response->setJSON(['uploaded' => 0, 'error' => ['message' => 'No file uploaded or invalid file.']]);
}
// MIME type and size validation (max 5MB, only images)
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif'];
if (!in_array($file->getMimeType(), $allowedMimes) || $file->getSizeByUnit('mb') > 5) {
return $this->response->setJSON(['uploaded' => 0, 'error' => ['message' => 'Invalid file type or size too large.']]);
}
// Move to writable uploads folder
$uploadPath = WRITEPATH . 'uploads/media/';
if (!is_dir($uploadPath)) {
mkdir($uploadPath, 0755, true);
}
$newName = $file->getRandomName();
$file->move($uploadPath, $newName);
// Generate public URL (assuming public/uploads/media is symlinked or served)
$url = base_url('uploads/media/' . $newName);
return $this->response->setJSON([
'uploaded' => 1,
'fileName' => $newName,
'url' => $url
]);
}
public function store()
{
// Form validation
$validation = \Config\Services::validation();
$validation->setRules([
'title' => 'required|min_length[3]',
'content' => 'required',
]);
if (!$validation->withRequest($this->request)->run()) {
return redirect()->back()->withInput()->with('errors', $validation->getErrors());
}
$data = $this->request->getPost(['title', 'content']);
$this->model->insert($data);
return redirect()->to('/ckeditor')->with('success', 'Post saved successfully!');
}
}
?>
7 Define a Route
use CodeIgniter\Router\RouteCollection;
$routes->get('ckeditor', 'Ckeditor::index');
$routes->post('ckeditor/upload', 'Ckeditor::upload', ['csrf' => true]); // Enable CSRF if not already
$routes->post('ckeditor/store', 'Ckeditor::store');
8 Create the View
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CKEditor 5 in CodeIgniter 4</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.ckeditor.com/ckeditor5/43.2.0/classic/ckeditor.js"></script> <!-- Latest as of 2026 -->
</head>
<body class="container mt-5">
<h1>Add New Post with CKEditor 5</h1>
<?php if (session()->getFlashdata('success')): ?>
<div class="alert alert-success"><?= session()->getFlashdata('success') ?></div>
<?php endif; ?>
<?php if (session()->getFlashdata('errors')): ?>
<div class="alert alert-danger">
<ul>
<?php foreach (session()->getFlashdata('errors') as $error): ?>
<li><?= $error ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<form action="<?= base_url('ckeditor/store') ?>" method="post">
<?= csrf_field() ?>
<div class="mb-3">
<label for="title" class="form-label">Title</label>
<input type="text" name="title" id="title" class="form-control" value="<?= old('title') ?>">
</div>
<div class="mb-3">
<label for="editor" class="form-label">Content</label>
<textarea name="content" id="editor" class="form-control" rows="10"><?= old('content') ?></textarea>
</div>
<button type="submit" class="btn btn-primary">Save Post</button>
</form>
<script>
ClassicEditor
.create(document.querySelector('#editor'), {
ckfinder: {
uploadUrl: '<?= base_url('ckeditor/upload') ?>'
},
toolbar: [
'heading', '|',
'bold', 'italic', 'link', 'bulletedList', 'numberedList', 'blockQuote', 'insertTable',
'|', 'imageUpload', '|',
'undo', 'redo'
]
})
.catch(error => {
console.error(error);
});
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
Set Up Upload Folder
Create the uploads folder manually (since CI4's writable is not public):
- In the project root, run: mkdir -p writable/uploads/media && chmod 755 writable/uploads/media
- To serve uploads publicly, create a symlink in public/:
ln -s ../writable/uploads public/uploads. - Or configure your web server (e.g., Apache/Nginx) to serve writable/uploads as /uploads.
9 Folder Structure
11 Run Web Server to Test the App
php spark serve
Visit http://localhost:8080/ckeditor in your browser. You should see the form with CKEditor. Add a title and content, upload images via the editor's toolbar, and save. The post will be stored in the database, and images in writable/uploads/media
.Troubleshooting
- Upload errors: Ensure the writable/uploads/media folder is writable (chmod 777 if needed for testing).
- CSRF issues: If CSRF is enabled globally in app/Config/Filters.php, it's handled automatically.
- Displaying saved content: To view posts, add a list method in the controller (e.g., fetch from model and use = $post->content ?> in a view, but escape if needed with esc() for security).
- Production tips: Use environment-specific configs, add more validation (e.g., file extensions), and consider cloud storage for images.
11 Conclusion
Reference URL
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, a MySQL database, and Composer installed.
Run `composer create-project codeigniter4/appstarter ci-4-ckeditor-app`, then `cd ci-4-ckeditor-app`.
Edit the `.env` file and set `database.default.hostname = localhost`, `database.default.database = your_db_name`, `username`, `password`, `DBDriver = MySQLi`, and `port = 3306`.
Add the CDN script in your view file: <script src='https://cdn.ckeditor.com/ckeditor5/41.4.2/classic/ckeditor.js'></script>.
Use `ClassicEditor.create(document.querySelector('#editor'), { ckfinder: { uploadUrl: 'upload' } })` in a script tag.
Run `php spark make:model PostModel` and `php spark make:migration AddPost`, then define the `posts` table and run `php spark migrate`.
In `PostModel.php`, change `protected $table = 'payments';` to `protected $table = 'posts';` to match the migration.
Run `php spark make:controller CkeditorController`, then implement `index()` for the form, `upload()` for handling images, and `store()` for saving data.
In the `upload()` method, get the file with `$this->request->getFile('upload')`, generate a random name, move it to the `media/` folder, and return JSON with the URL.
Images are saved in the `media/` directory in the project root.
Ensure the `media/` folder exists and has write permissions (e.g., 755 or 777). Also, check if the upload URL route is correctly defined.
In the `store()` method, use `$postModel->insert(['title' => $title, 'description' => $description])` where description includes HTML with image tags.
In `app/Config/Routes.php`, add `$routes->get('ckeditor', 'CkeditorController::index');`, `$routes->post('upload', 'CkeditorController::upload');`, and `$routes->post('store', 'CkeditorController::store');`.
Start the server with `php spark serve`, then visit `http://localhost:8080/ckeditor` to test editing, image upload, and form submission.
