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 Drag and Drop File Upload with Dropzone.js and Vite

๐Ÿ› ๏ธ Laravel Tutorials4 min read

Laravel 13 Drag and Drop File Upload with Dropzone.js and Vite

Muhammad Yasir Amin

Muhammad Yasir Amin

Senior Full Stack Engineerโ€ขNov 12, 2026
Laravel 13 Drag and Drop File Upload with Dropzone.js and Vite

Dropzone.js turns a box on your page into a drag and drop upload area with previews and progress bars. In Laravel 13 you install it with npm, load it through Vite, point it to a route that validates and stores each file, and send the CSRF token in a request header. This tutorial builds the whole flow for image uploads.

Targets: Laravel 13 (PHP 8.3 to 8.5), Vite and the dropzone npm package. Check the Dropzone documentation for any option changes in the version you install.

Step 1: Install Dropzone

Code
npm install dropzone

Step 2: Import it in app.js

JavaScript
import Dropzone from 'dropzone';
import 'dropzone/dist/dropzone.css';

Dropzone.autoDiscover = false;

const element = document.getElementById('image-dropzone');

if (element) {
    new Dropzone(element, {
        url: element.dataset.url,
        paramName: 'file',
        maxFilesize: 5,
        acceptedFiles: 'image/jpeg,image/png,image/webp',
        addRemoveLinks: true,
        headers: {
            'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
            'Accept': 'application/json',
        },
    });
}

Dropzone.autoDiscover = false stops Dropzone from attaching itself to every .dropzone element automatically, so your own setup is the only one that runs. maxFilesize is in megabytes. paramName is the form field name that arrives in Laravel, here file.

Step 3: Add the Blade page

HTML / XML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>Upload images</title>
    @vite(['resources/js/app.js'])
</head>
<body>
    <form id="image-dropzone" class="dropzone" data-url="{{ route('uploads.store') }}"></form>
</body>
</html>

The CSRF token is in a meta tag so the script can read it. Without it, uploads fail with a 419 error.

Step 4: Create the route and controller

CSS
use App\Http\Controllers\UploadController;
use Illuminate\Support\Facades\Route;

Route::view('/uploads', 'uploads');
Route::post('/uploads', [UploadController::class, 'store'])->middleware('auth')->name('uploads.store');
JavaScript
<?php

namespace App\Http\Controllers;

use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class UploadController extends Controller
{
    public function store(Request $request): JsonResponse
    {
        $request->validate([
            'file' => ['required', 'image', 'mimes:jpg,jpeg,png,webp', 'max:5120'],
        ]);

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

        return response()->json(['path' => $path, 'url' => asset('storage/'.$path)]);
    }
}

Each dropped file is sent as its own request, so the controller handles one file at a time. Run php artisan storage:link once so the URL returned in the response can be opened in a browser. The server validation matches the client settings (5 MB, image types). Never rely on the browser checks alone.

How do I show server validation errors?

When validation fails, Laravel returns a 422 JSON response. Dropzone shows a generic error by default. Handle the error event to show Laravel's message:

JavaScript
const dropzone = new Dropzone(element, { /* same options as above */ });

dropzone.on('error', (file, response) => {
    const message = typeof response === 'string'
        ? response
        : (response.errors?.file?.[0] ?? response.message ?? 'Upload failed.');

    file.previewElement.querySelector('.dz-error-message span').textContent = message;
});

Use this version instead of the one in Step 2 if you want custom messages. Keep a reference to the Dropzone instance in a variable so you can attach events to it.

How do I delete an uploaded file with the remove link?

Save the stored path from the success response and send a delete request when the user removes the file:

JavaScript
dropzone.on('success', (file, response) => {
    file.serverPath = response.path;
});

dropzone.on('removedfile', (file) => {
    if (!file.serverPath) return;

    fetch('/uploads', {
        method: 'DELETE',
        headers: {
            'Content-Type': 'application/json',
            'Accept': 'application/json',
            'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
        },
        body: JSON.stringify({ path: file.serverPath }),
    });
});

On the server, validate that the path starts with uploads/ and belongs to the current user before deleting it with Storage::disk('public')->delete($path). Never delete a path taken directly from the request without checking it, because a user could try to delete other files.

How do I check that it works?

  1. Run npm run dev, open /uploads while logged in and drag a JPG into the box.
  2. Check that a progress bar appears and the file is saved in storage/app/public/uploads.
  3. Drop a PDF and confirm that Dropzone rejects it. Then bypass the browser check with a tool such as curl and confirm that Laravel returns 422.

Common mistakes

  • 419 error: the CSRF header is missing.
  • Dropzone attached twice: set Dropzone.autoDiscover = false.
  • No styles: import the Dropzone CSS file.
  • Mismatched limits: align Dropzone's maxFilesize with Laravel's max and PHP's upload_max_filesize.
  • Trusting file types in the browser only: always validate on the server.

FAQ

Can I upload several files at once?

Yes. Dropzone sends each file as a separate request by default. Use uploadMultiple: true if you want one request with all files, and change the controller to accept an array.

Can Dropzone work inside a larger form?

Yes. A common pattern is to upload files immediately, collect the returned paths in hidden inputs, and submit them with the main form.

Is there an alternative?

Livewire has built-in file uploads, and plain HTML inputs work for simple cases. Dropzone is useful when you want previews and drag and drop without a framework.

References

Article Index & Topic Tags
LaravelViteFile-UploadDropzone

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.