Browse by section

Web Design 日本語

Upload Quill Images to Laravel With Ajax

This article covers uploading images selected in Quill to a Laravel backend over Ajax.

The short answer: replace the image button’s handler, POST the file with FormData, and insert the returned URL into the document. Left as-is, Quill embeds the image as Base64 and the content string balloons.

The server side is a few lines with Laravel’s validation and storage.

Sponsored

Setting up the Laravel project

Skip this if Laravel is already installed.

composer create-project --prefer-dist laravel/laravel your-project-name
cd your-project-name

Setting up Quill

Quill can be loaded from a CDN or served from your own public directory.

public/
  └── quill/
      ├── quill.js
      └── quill.css
<div id="editor"></div>

<script src="{{ asset('quill/quill.js') }}"></script>
<script>
  const quill = new Quill('#editor', {
    theme: 'snow'
  });
</script>

Sponsored

Uploading the image over Ajax

Create the route and controller that will receive the file.

php artisan make:controller ImageUploadController

On the client, replace the toolbar’s image handler:

const toolbar = quill.getModule('toolbar');

toolbar.addHandler('image', function () {
  const input = document.createElement('input');
  input.setAttribute('type', 'file');
  input.setAttribute('accept', 'image/*');
  input.click();

  input.onchange = async () => {
    const formData = new FormData();
    formData.append('image', input.files[0]);

    const res = await fetch('/upload', {
      method: 'POST',
      headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content },
      body: formData,
    });
    const data = await res.json();

    const range = quill.getSelection(true);
    quill.insertEmbed(range.index, 'image', data.url);
    quill.setSelection(range.index + 1);
  };
});

Writing the controller that receives the image

Validate on the receiving end, and let the server generate the filename. Reusing the uploaded file’s original name is a risk you do not need to take.

// ImageUploadController.php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

class ImageUploadController extends Controller
{
    public function upload(Request $request)
    {
        // always validate type and size
        $request->validate([
            'image' => ['required', 'image', 'mimes:jpeg,png,webp', 'max:5120'],
        ]);

        // let Laravel generate the filename
        $path = $request->file('image')->store('uploads', 'public');

        return response()->json([
            'success' => true,
            'image_url' => Storage::url($path),
        ]);
    }
}

There are two reasons not to reuse the original filename. One is that names containing non-Latin characters or symbols may not survive the filesystem. The other is that a crafted filename can write to somewhere you did not intend. store() generates a random name, which avoids both.

The image rule inspects the file’s contents rather than trusting the extension, so a renamed file is rejected. max:5120 is in kilobytes, giving a 5MB ceiling.

Storage::url() requires the storage symlink:

php artisan storage:link

Sponsored

What to check on the server

Always constrain the file type and size on the endpoint that receives uploads.

Without limits, anything can be posted at any size. Laravel’s validation covers it:

$request->validate([
    'image' => ['required', 'image', 'mimes:jpeg,png,webp', 'max:5120'],
]);

$path = $request->file('image')->store('uploads', 'public');

return response()->json([
    'url' => Storage::url($path),
]);

max:5120 is in kilobytes, so that is a 5MB ceiling. The image rule inspects the file’s actual contents rather than trusting the extension, which blocks a renamed file.

Storage::url() needs the storage symlink in place:

php artisan storage:link

Uploaded images arrive at whatever size the user had. The reasoning for compressing them is in building an image compression GUI in Python — photos to JPEG, flat graphics to a reduced palette. The rule holds regardless of language.

Related reading

If Quill is not set up yet, start with adding the Quill rich text editor to Laravel.

The client-side module configuration is covered in inserting images as img tags in Quill, and adding your own toolbar buttons in adding a custom toolbar button to Quill.

Summary

This article covered uploading images selected in Quill to a Laravel backend over Ajax and inserting the returned URL.

Three things decide whether it works:

  • Append a File object to FormData, not the image’s src string
  • Validate type and size on the server, and let Laravel name the file
  • Read the CSRF token from a <meta> tag so it survives moving to an external script