CKEditor 5 is a modern, powerful WYSIWYG editor. When paired with CKFinder (the official file manager/uploader from the CKEditor team), it allows users to upload and browse images directly in PHP apps.

How to Integrate CKEditor 5 with CKFinder for Image Upload in PHP

Table Of Content

1 Prerequisites

  • PHP 7.4+
  • Apache/Nginx web server
  • MySQL/MariaDB
  • Writable uploads/ folder (chmod 755 or 777)

2 Introduction

This tutorial uses a simple custom PHP upload handler compatible with CKEditor 5's CKFinder config option. For full CKFinder features (browsing, folders, image editing), download and install the official CKFinder PHP connector from ckeditor.com.

3 Create Project and Config Database

3.1 Create Project Folder "ckeditor-app"

Create Project Folder "ckeditor-app" in root directory store all project files

3.2 Configure MySql Database

We will create table to store the post information and images in the database.

dbconfig.php (Database Configuration)
In this dbconfig.php file, database setting variables are defined.

<?php 
// Database settings 
define('DB_HOST', 'localhost'); 
define('DB_USERNAME', 'root'); 
define('DB_PASSWORD', ''); 
define('DB_NAME', 'blog'); 
?>

Database Table Creation (Run this SQL in your MySQL tool, e.g., phpMyAdmin)

CREATE TABLE `posts` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `title` VARCHAR(255) NOT NULL,
  `description` TEXT,
  `created` DATETIME DEFAULT CURRENT_TIMESTAMP,
  `updated` DATETIME DEFAULT NULL,
  `status` TINYINT(4) DEFAULT 1,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

4 Include CKEditor 5

In your "index.php" file, include CKEditor 5's CDN link within the head section:

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

5 Create index.php File (Main Editor Form)

Create "index.php" File and place following code

 <?php
session_start();
include 'dbconfig.php';
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CKEditor 5 Image Upload PHP Example</title>
    <!-- Bootstrap 5 CDN for styling -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
    <!-- CKEditor 5 CDN -->
    <script src="https://cdn.ckeditor.com/ckeditor5/43.0.0/classic/ckeditor.js"></script>
    <style>
        .ck-editor__editable { min-height: 400px; }
    </style>
</head>
<body>
    <div class="container mt-5">
        <?php if (isset($_SESSION['message'])): ?>
            <div class="alert alert-success"><?php echo $_SESSION['message']; unset($_SESSION['message']); ?></div>
        <?php endif; ?>

        <h2 class="mb-4">Create Post with CKEditor 5</h2>
        <form method="POST" action="store.php">
            <div class="mb-3">
                <label for="title" class="form-label">Title</label>
                <input type="text" class="form-control" id="title" name="title" required>
            </div>
            <div class="mb-3">
                <label for="editor" class="form-label">Content</label>
                <textarea name="description" id="editor"></textarea>
            </div>
            <button type="submit" class="btn btn-primary">Save Post</button>
        </form>
    </div>

    <script>
        ClassicEditor
            .create(document.querySelector('#editor'), {
                ckfinder: {
                    uploadUrl: 'upload.php'  // Your custom PHP upload endpoint
                },
                toolbar: ['heading', '|', 'bold', 'italic', 'link', 'bulletedList', 'numberedList', 'blockQuote', 'insertImage', 'undo', 'redo']
            })
            .catch(error => console.error(error));
    </script>
    <!-- Bootstrap JS (optional for interactivity) -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
</body>
</html>

6 Create upload.php File (Image Upload Handler)

Create "upload.php" File and place the code in /upload.php.

 <?php
header('Content-Type: application/json');

if (!empty($_FILES['upload'])) {
    $file = $_FILES['upload'];
    $targetDir = "uploads/";
    if (!is_dir($targetDir)) {
        mkdir($targetDir, 0755, true);
    }

    $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
    $allowed = ['jpg', 'jpeg', 'png', 'gif'];
    if (!in_array($ext, $allowed)) {
        echo json_encode(['uploaded' => 0, 'error' => ['message' => 'Invalid file type. Only JPG, JPEG, PNG, GIF allowed.']]);
        exit;
    }

    // Secure filename to prevent collisions/overwrites
    $newName = md5(time() . $file['name']) . '.' . $ext;
    $targetFile = $targetDir . $newName;

    if (move_uploaded_file($file['tmp_name'], $targetFile)) {
        // Adjust URL to match your server path (e.g., if in subfolder, prepend accordingly)
        $url = '/' . basename(__DIR__) . '/' . $targetFile;
        echo json_encode([
            'uploaded' => 1,
            'fileName' => $file['name'],
            'url' => $url
        ]);
    } else {
        echo json_encode(['uploaded' => 0, 'error' => ['message' => 'File upload failed. Check server permissions.']]);
    }
} else {
    echo json_encode(['uploaded' => 0, 'error' => ['message' => 'No file received.']]);
}
?>

7 Create store.php File

Create "store.php" File and place following code

<?php
session_start();
include 'dbconfig.php';

$conn = new mysqli(DB_HOST, DB_USERNAME, DB_PASSWORD, DB_NAME);
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}

if (isset($_POST['title']) && isset($_POST['description'])) {
    $stmt = $conn->prepare("INSERT INTO posts (title, description) VALUES (?, ?)");
    $stmt->bind_param("ss", $_POST['title'], $_POST['description']);
    if ($stmt->execute()) {
        $_SESSION['message'] = 'Post created successfully!';
    } else {
        $_SESSION['message'] = 'Error saving post: ' . $stmt->error;
    }
    $stmt->close();
} else {
    $_SESSION['message'] = 'Invalid form data.';
}

$conn->close();
header("Location: index.php");
exit;
?>

8 Folder Structure

9 Testing Steps

  • Place all files in your project folder.
  • Ensure uploads/ is created and writable.
  • Update dbconfig.php with your actual DB credentials.
  • Run the SQL to create the table.
  • Open index.php in your browser.
  • Enter a title, add content in the editor, upload an image via the image toolbar icon (or drag-and-drop).
  • Submit – it should save to the DB and show a success message.
  • Check the posts table for the entry (images are stored in uploads/, paths in the description HTML).

10 Conclusion

This setup gives you a functional CKEditor 5 + image upload in PHP. For advanced features (thumbnails, folders), upgrade to 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 version greater than 7.4, a web server (e.g., Apache/Nginx), and a MySQL database.

Create a database, then run the provided SQL to create the `posts` table with fields: id, title, description, created, updated, status.

Add the CDN script in the `<head>`: <script src=`https://cdn.ckeditor.com/ckeditor5/41.4.2/classic/ckeditor.js`> </script> (version may vary).

Use `ClassicEditor.create(document.querySelector('#editor'), { ckfinder: { uploadUrl: 'upload.php' } })`.

It handles image uploads from CKEditor/CKFinder, moves the file to the `uploads/` folder with a unique name, and returns a JSON response with the URL.

CKFinder sends the file as `upload`, but the tutorial uses `uploadedfile`. Update to `$_FILES['upload']` to match standard CKFinder behavior.

Images are saved in the `uploads/` directory with a unique filename (MD5 hash + original extension).

The form submits to `store.php`, which inserts the title and description (including img tags with uploaded image URLs) into the `posts` table.

Ensure the `uploads/` directory exists in the project root and has write permissions (e.g., 755 or 777) for the web server user.

Verify `upload.php` returns proper JSON ({`uploaded`:1,`url`:`path/to/image.jpg`}), check file field name mismatch, and ensure no PHP errors (enable error reporting).

No, it uses direct `$_REQUEST` concatenation. For production, switch to prepared statements or PDO to prevent SQL injection.

The built-in CKFinder plugin (enabled via `ckfinder: { uploadUrl }`) provides a browse server feature automatically.

Yes, modify the `$target_path` in `upload.php` and add validation for extensions/size.

Place files in your web root, create the `uploads/` folder (writable), set up the database, visit `index.php`, add content/images, and save.