PHP & MySQL

CKEditor - Image & Audio File Upload with PHP

Configure CKEditor for file uploads - image and audio embedding, PHP server handler, drag-and-drop, and security.

What is CKEditor?

CKEditor is a popular WYSIWYG rich text editor for web applications. It allows users to compose formatted content - including embedded images, audio, and video - directly in the browser. This guide covers setting up file upload functionality with a PHP backend.

CKEditor 5 Setup with Upload Adapter

<!-- Include CKEditor 5 from CDN -->
<script src="https://cdn.ckeditor.com/ckeditor5/41.0.0/classic/ckeditor.js"></script>

<textarea id="editor"></textarea>

<script>
ClassicEditor
  .create(document.querySelector('#editor'), {
    // Simple upload adapter
    simpleUpload: {
      uploadUrl: 'upload.php',
      headers: {
        'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
      }
    },
    toolbar: [
      'heading', '|', 'bold', 'italic', 'link',
      '|', 'imageUpload', 'mediaEmbed',
      '|', 'bulletedList', 'numberedList',
      '|', 'undo', 'redo'
    ]
  })
  .catch(error => console.error(error));
</script>

PHP Upload Handler

<?php
// upload.php - handles CKEditor Simple Upload Adapter
header('Content-Type: application/json');

$uploadDir = __DIR__ . '/uploads/';
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp',
                 'audio/mpeg', 'audio/ogg', 'audio/wav'];
$maxSize = 10 * 1024 * 1024;  // 10MB

if ($_SERVER['REQUEST_METHOD'] !== 'POST' || empty($_FILES['upload'])) {
    http_response_code(400);
    echo json_encode(['error' => ['message' => 'No file uploaded']]);
    exit;
}

$file = $_FILES['upload'];

// Validate
if ($file['error'] !== UPLOAD_ERR_OK) {
    echo json_encode(['error' => ['message' => 'Upload error: ' . $file['error']]]);
    exit;
}

$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);

if (!in_array($mimeType, $allowedTypes)) {
    echo json_encode(['error' => ['message' => 'File type not allowed']]);
    exit;
}

if ($file['size'] > $maxSize) {
    echo json_encode(['error' => ['message' => 'File too large (max 10MB)']]);
    exit;
}

// Generate safe filename
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
$safeName = bin2hex(random_bytes(16)) . '.' . strtolower($ext);

if (!is_dir($uploadDir)) mkdir($uploadDir, 0755, true);

if (move_uploaded_file($file['tmp_name'], $uploadDir . $safeName)) {
    $url = '/uploads/' . $safeName;
    echo json_encode(['url' => $url]);
} else {
    echo json_encode(['error' => ['message' => 'Failed to save file']]);
}

Audio & Media Embedding

// For audio files, create a custom CKEditor plugin
// that renders <audio> tags instead of <img>

// Or use the mediaEmbed plugin for external sources:
mediaEmbed: {
  previewsInData: true,
  providers: [
    { name: 'youtube', url: /youtube\.com/ },
    { name: 'soundcloud', url: /soundcloud\.com/ },
  ]
}

Security Best Practices

RiskMitigation
Executable file uploadValidate MIME type with finfo, not just extension
Path traversalGenerate random filenames, never use user input in paths
XSS via SVGBlock SVG uploads or sanitize with a library
Denial of serviceEnforce file size limits, rate limiting
Directory listingAdd .htaccess or nginx config to block directory browsing

CKEditor 4 (Legacy)

// CKEditor 4 uses a different upload config:
CKEDITOR.replace('editor', {
  filebrowserUploadUrl: 'upload.php?type=Files',
  filebrowserImageUploadUrl: 'upload.php?type=Images',
});

// The PHP response format differs for CKEditor 4:
// echo "<script>window.parent.CKEDITOR.tools.callFunction(
//   {$_GET['CKEditorFuncNum']}, '$url', '');</script>";

Last updated: 2026 • Browse all courses