Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Dropzone Laravel Package

enyo/dropzone

A Laravel package that integrates Dropzone.js for drag-and-drop file uploads, providing server-side upload handling, validation, and storage helpers so you can add multiple-file upload forms with progress feedback to your application quickly.

View on GitHub
Deep Wiki
Context7

Getting Started

Know what you're installing first. enyo/dropzone is the Composer-packaged distribution of the Dropzone.js front-end library — not a Laravel service provider. There is no config/dropzone.php, no publishable migrations, no Dropzone facade, and no server-side upload handler shipped with it. The "Laravel-friendly API" is Laravel itself: a route, a FormRequest, and Storage. Budget an hour, not a sprint.

Install and get the assets in place.

composer require enyo/dropzone

Then either copy the dist files out of vendor/enyo/dropzone/dist/ or — strongly preferred if you already run Vite — install the npm package instead and import it:

// resources/js/app.js
import Dropzone from 'dropzone';
import 'dropzone/dist/dropzone.css';
Dropzone.autoDiscover = false; // see Gotchas
window.Dropzone = Dropzone;

Where to look first: vendor/enyo/dropzone/dist/ (dropzone.js, dropzone.min.js, dropzone.css, basic.css) and the upstream README in the package root. Treat the vendor README as the real documentation — it is the canonical Dropzone.js docs, not package-specific Laravel docs.

First use case — a Blade form posting to a plain Laravel route.

<meta name="csrf-token" content="{{ csrf_token() }}">
<form action="{{ route('uploads.store') }}" class="dropzone" id="mediaDropzone"></form>
// routes/web.php
Route::post('/uploads', [UploadController::class, 'store'])->name('uploads.store');

// UploadController
public function store(Request $request)
{
    $request->validate([
        'file' => ['required', 'file', 'mimes:jpg,jpeg,png,webp,pdf', 'max:10240'],
    ]);

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

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

That's the whole loop: preview + progress bar from the JS, validation and storage from Laravel. Everything below is refinement of those two halves.

Implementation Patterns

Configure via the options object, not inline attributes. The Dropzone.options.<elementId> convention is the idiomatic path and keeps JS out of Blade:

Dropzone.options.mediaDropzone = {
  url: '/uploads',
  paramName: 'file',
  maxFilesize: 10,          // MB
  acceptedFiles: 'image/*,.pdf',
  maxFiles: 5,
  addRemoveLinks: true,
  headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content },
  init() {
    this.on('success', (file, res) => { file.serverPath = res.path; });
    this.on('error', (file, msg) => console.warn(msg));
  },
};

Note the id-to-key mapping: <form id="mediaDropzone"> becomes Dropzone.options.mediaDropzone.

Validation belongs in a FormRequest, and you should mirror it explicitly. The JS acceptedFiles/maxFilesize are UX hints only. Enforce mimes, max, and dimensions server-side. Return 422 with a JSON body so Dropzone can render the message on the preview rather than a bare red X.

Multi-file mode changes the payload shape. With uploadMultiple: true, Dropzone posts files[] and Laravel sees an array:

$request->validate([
    'files'   => ['required', 'array', 'max:5'],
    'files.*' => ['file', 'mimes:jpg,png,pdf', 'max:10240'],
]);
foreach ($request->file('files') as $file) { $file->store('uploads', 'public'); }

$request->file('file') silently returns null in this mode — the single most common "it uploads but nothing is saved" bug.

Chunked uploads for anything large. Dropzone handles slicing client-side; you concatenate server-side. A pragmatic pattern is to append each chunk to a temp file keyed by dzchunkindex/dzuuid, then move it into place when dzchunkindex === dztotalchunkcount - 1:

{ chunking: true, forceChunking: true, chunkSize: 2_000_000, parallelChunkUploads: false, retryChunks: true }

Store chunks on a local disk (not S3) under chunks/{dzuuid} and clean up on completion or failure.

Multiple dropzones on one page requires manual instantiation, since autoDiscover would bind the same options everywhere:

new Dropzone('#cover', { url: '/cover', paramName: 'file' });
new Dropzone('#gallery', { url: '/gallery', paramName: 'files', uploadMultiple: true });

Livewire / Inertia / Alpine integration. Wrap the element in wire:ignore and re-initialise on the framework's lifecycle hook; destroy the instance on teardown (dz.destroy()) or you'll accumulate listeners and duplicate requests after every re-render. With Alpine, initialise in x-init and guard against double-init.

Attach domain context to the upload using params, so the server can validate ownership:

params: { gallery_id: {{ $gallery->id }} },

and validate with Rule::exists('galleries', 'id')->where('user_id', auth()->id()).

Deleting files is your job. Dropzone's addRemoveLinks only removes the preview. Wire removedfile to a DELETE route, and treat the DB row as the source of truth so orphans don't accumulate.

Hydrating existing files on page load is a standard trick for edit screens — call emit('addedfile', mockFile) and emit('thumbnail', mockFile, url) for each stored asset so the user sees what's already there.

Gotchas and Tips

  • There is no Laravel code in this package. No service provider, no config, no validation rule, no storage helper. Any tutorial implying php artisan vendor:publish for enyo/dropzone is wrong or describing a different, unmaintained wrapper. Plan for writing the controller yourself.
  • 419 errors on every upload mean CSRF isn't reaching the request. Send the token in headers, or add the endpoint to VerifyCsrfToken::$except if it uses a signed URL. Do not disable CSRF globally.
  • The maxFilesize unit mismatch. Dropzone's maxFilesize is megabytes; Laravel's max: rule is kilobytes. maxFilesize: 2 alongside max:2048 is correct; max:2 will reject almost everything with a message that looks like a bug.
  • PHP and web-server limits fire before Laravel does. When post_max_size is exceeded, Laravel receives an empty body and often reports a CSRF failure instead of a size error. Check upload_max_filesize, post_max_size, max_file_uploads, max_execution_time in php.ini, plus nginx client_max_body_size and fastcgi_read_timeout for chunked uploads.
  • Return JSON errors, always. Dropzone renders plain-text responses poorly and an HTML 500 page as an unreadable blob. Catch ValidationException (Laravel does this for you on JSON requests) and return ['error' => $message] or a message key consistently.
  • Dropzone.autoDiscover = false must be set before any element with class dropzone is parsed, otherwise you get a phantom instance bound to defaults plus your configured one — doubled uploads. Turbo/Hotwire and Livewire navigation re-trigger auto-discovery, so disable it globally and init explicitly.
  • Don't nest the Dropzone element inside another <form>. Dropzone creates its own form element and hidden inputs; nested forms produce unpredictable submits and duplicate files.
  • Nested-form/duplicate-input issues also break FormData-based fallbacks. If you also need a no-JS fallback, add a separate plain <input type="file" multiple> form rather than trying to reuse the same markup.
  • Client acceptedFiles is not security. Browsers report MIME from extension; validate with mimes: (which inspects content) and consider dimensions: for images. Never store with the client-supplied filename — use store()/hashName().
  • Preview memory leaks show up as a browser hang after a few dozen images. Use thumbnailWidth/thumbnailMethod rather than full-size previews, and revoke object URLs on removedfile.
  • Disk choice matters. store('uploads', 'public') needs php artisan storage:link before Storage::url() resolves. For production volume, prefer direct-to-S3 with presigned URLs (uploadMultiple: false, one request per file) so large files never touch PHP memory.
  • queuecomplete is the right hook for "save the collection" UX and for triggering a Livewire refresh — not success, which fires per file.
  • Licence check before shipping. The package metadata reports NOASSERTION; Dropzone.js itself is MIT, but confirm the bundled licence file in vendor/enyo/dropzone/ since the manifest is ambiguous. Also note there are no Laravel version constraints because there's no PHP — Composer won't warn you about incompatibility, and equally won't upgrade you out of a problem.
  • Debugging playbook: open the Network tab and confirm the request payload shape (file vs files[]), then check the response body is JSON, then check Dropzone.options.<id> actually applied (log inside init). getRejectedFiles() and the error event with the full xhr argument will tell you whether the rejection was client-side (accept callback, size) or server-side.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console