Muhammad Yasir Amin

Loading websiteโ€ฆ

More from Muhammad Yasir Amin

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Subscribe on YouTube

Laravel 13 Summernote Image Upload Tutorial

๐Ÿ› ๏ธ Laravel Tutorials4 min read

Laravel 13 Summernote Image Upload Tutorial

Muhammad Yasir Amin

Muhammad Yasir Amin

Senior Full Stack Engineerโ€ขNov 6, 2026
Laravel 13 Summernote Image Upload Tutorial

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?

Code
php artisan storage:link

This 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?

CSS
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?

JavaScript
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:

JavaScript
<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?

  1. Open the editor page and click the picture icon.
  2. Choose a small PNG. The image should appear in the editor.
  3. In browser developer tools, confirm the image src starts with your site URL and /storage/editor/, not data:image.
  4. Check that the file exists in storage/app/public/editor.
  5. 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 the APP_URL does not match the site address.
  • 422 error. The file failed validation, for example it is too large.
  • PHP limits. upload_max_filesize and post_max_size in php.ini must 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.

References

Article Index & Topic Tags
LaravelSummernoteFile-UploadValidation

YouTube Channel

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Muhammad Yasir Amin
Available for Contracts
Author & Solutions Architect

Senior Full Stack Engineer

Independent Senior Full Stack Engineer working on Laravel, React and Next.js applications, APIs and delivery workflows.

Laravel & PHP ApplicationsReact & Next.js InterfacesREST APIs & IntegrationsDatabase Queries & DebuggingDocker Development WorkflowsClear Scope & Handover
Discuss Your Next Step

What Does Your Application Need Next?

Share your current system, the problem you want to solve and what a useful result looks like. We can identify the right starting point and agree on a practical scope.

Technical Audit

Find the Next Useful Fix

Application Development

Scoped, Reviewable Milestones

Ongoing Support

Agreed Capacity and Priorities

Share your goals, current stack and requirements before the call.