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
Fileobject toFormData, not the image’ssrcstring - 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