To upload images from the Summernote editor in Laravel 13, run php artisan storage:link, create a route and controller that validates the file and stores it on the public disk, return the image URL as JSON, and wire Summernote's onImageUpload callback to post the file to that route and call insertImage with the returned URL. Without this, Summernote embeds images as huge base64 strings in your post body.
This tutorial uses Laravel 13 (PHP 8.3 or newer) and assumes Summernote is already loaded on a Blade page with jQuery. Check Summernote's own site for the current download or CDN links and versions.
Why not keep Summernote's default base64 images?
- The database column grows quickly, since each image is stored as text inside the post.
- Pages get heavy and slow to render.
- You cannot resize, cache or serve images from a CDN.
Uploading real files fixes all three.
Step 1: How do I make uploads publicly reachable?
php artisan storage:linkThis creates a symbolic link from public/storage to storage/app/public, so files stored on the public disk are reachable by URL.
Step 2: How do I create the upload route?
use App\Http\Controllers\EditorImageController;
Route::post('/editor/images', [EditorImageController::class, 'store'])
->middleware('auth')
->name('editor.images.store');Keep the route behind authentication so strangers cannot fill your disk.
Step 3: What does the controller look like?
namespace App\Http\Controllers;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class EditorImageController extends Controller
{
public function store(Request $request): JsonResponse
{
$request->validate([
'image' => ['required', 'image', 'mimes:jpg,jpeg,png,webp', 'max:2048'],
]);
$path = $request->file('image')->store('editor', 'public');
return response()->json(['url' => Storage::disk('public')->url($path)]);
}
}The image rule checks that the file is an image, mimes restricts the extensions, and max:2048 limits the size to 2048 kilobytes. Laravel names the stored file with a random hash, which avoids name collisions. I ran php -l on this controller to confirm its syntax. I did not run a real upload for this article.
I left out SVG on purpose. SVG files can contain scripts, so only allow them if you sanitise them.
Step 4: How do I connect Summernote to the route?
Add the CSRF token as a meta tag in your layout, then configure the callback:
<meta name="csrf-token" content="{{ csrf_token() }}">
<textarea id="body" name="body"></textarea>
<script>
$(function () {
$('#body').summernote({
height: 320,
callbacks: {
onImageUpload: function (files) {
Array.from(files).forEach(uploadImage);
}
}
});
function uploadImage(file) {
const data = new FormData();
data.append('image', file);
fetch("{{ route('editor.images.store') }}", {
method: 'POST',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
'Accept': 'application/json'
},
body: data
})
.then(function (response) {
if (!response.ok) { throw new Error('Upload failed'); }
return response.json();
})
.then(function (json) {
$('#body').summernote('insertImage', json.url);
})
.catch(function () {
alert('Image upload failed. Check the file type and size.');
});
}
});
</script>The Accept: application/json header makes Laravel return validation errors as JSON, not as a redirect to a form page. Summernote's onImageUpload callback replaces its default base64 behaviour, and insertImage places the image at the cursor.
Step 5: How do I save and show the post safely?
The editor produces HTML. When you display it with {!! $post->body !!}, you output raw HTML, so a malicious user could store a script. Sanitise the HTML on save with a library such as HTML Purifier, or only allow trusted authors to write posts. Never output untrusted HTML unescaped.
How do I check that it works?
- Open the editor page and click the picture icon.
- Choose a small PNG. The image should appear in the editor.
- In browser developer tools, confirm the image
srcstarts with your site URL and/storage/editor/, notdata:image. - Check that the file exists in
storage/app/public/editor. - Try a 5 MB file. The upload should fail with the alert.
What are the common mistakes?
- 419 error. The CSRF token is missing from the request headers.
- 404 on the image URL. You forgot
php artisan storage:link, or theAPP_URLdoes not match the site address. - 422 error. The file failed validation, for example it is too large.
- PHP limits.
upload_max_filesizeandpost_max_sizeinphp.inimust be at least as large as your Laravel limit. - Public disk on a multi-server setup. Local files are not shared between servers. Use a shared disk such as S3 when you scale out.
- Orphaned files. Images uploaded and then removed from the post remain on disk. Clean them up with a scheduled job if that matters.
FAQ
Does this work with Laravel 12?
Yes. The file storage and validation APIs used here are the same.
Can I store the images on S3?
Yes. Configure an S3 disk, then use that disk name in store('editor', 's3') and Storage::disk('s3').
Is Summernote still a good choice?
It works and is simple, but it depends on jQuery. For a new project, compare it with other editors based on your front-end stack.



