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

Livewire Dropzone Laravel Package

dasundev/livewire-dropzone

Livewire Dropzone adds drag-and-drop file uploads to your Livewire components with minimal setup. Easily handle dropped files, previews, and validation in your Laravel apps.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and verify the version you get.

composer require dasundev/livewire-dropzone

Then pin it in composer.json ("dasundev/livewire-dropzone": "^3.0") rather than floating. The changelog shows real breaking changes across majors — v2.0.0 removed the built-in dropzone label, and v1 → v2 changed markup/behavior. Know which major you're on before copying snippets from blog posts.

2. First place to look: the vendor Blade view.

The README is intentionally thin and the real documentation lives off-site at dasun.dev/docs/livewire-dropzone. Before writing code, open:

vendor/dasundev/livewire-dropzone/resources/views/
vendor/dasundev/livewire-dropzone/src/

That view is the authoritative list of props, slots, and CSS classes for your pinned version. It is faster and more reliable than guessing from the changelog, and it tells you exactly which dz- prefixed classes you can style.

3. First use case: a single avatar/profile photo in an existing form.

This is the lowest-risk integration because it exercises the whole path (drop → Livewire temp upload → validation → persist) without array handling.

use Livewire\Component;
use Livewire\WithFileUploads;

class ProfilePhoto extends Component
{
    use WithFileUploads;

    public $photo = null;

    protected $rules = [
        'photo' => 'required|image|max:2048',
    ];

    public function save()
    {
        $this->validate();

        $path = $this->photo->store('profile-photos', 'public');

        auth()->user()->update(['photo' => $path]);

        $this->reset('photo');
    }
}
<form wire:submit="save">
    <livewire:dropzone
        wire:model="photo"
        :rules="['image', 'max:2048']"
        :multiple="false"
    />

    @error('photo')
        <span class="text-red-600">{{ $message }}</span>
    @enderror

    <button type="submit">Save</button>
</form>

Two things to internalize from day one:

  • The :rules prop is a client/UX convenience applied by the component before it syncs the file upward. Your server-side $rules and $this->validate() are the actual security boundary.
  • The property must be initialized (= null, = []). Early releases had bugs around uninitialized $file/$files (fixed in 1.0.5 and 2.0.1); initializing costs nothing and avoids resurrecting those failures.

If you only remember one thing: you are not uploading through Dropzone.js — you are uploading through Livewire's temporary file upload pipeline. Every Livewire limit, disk config, and cleanup rule applies.


Implementation Patterns

Pattern 1: Multi-file gallery / attachments

use Livewire\WithFileUploads;

public $files = [];

protected $rules = [
    'files'   => 'array|max:10',
    'files.*' => 'image|max:5120',
];
<livewire:dropzone
    wire:model="files"
    :rules="['image', 'max:5120']"
    :multiple="true"
/>

Persist in one place, never in a loop inside render():

public function save()
{
    $this->validate();

    foreach ($this->files as $file) {
        $this->post->attachments()->create([
            'path' => $file->store('attachments', 'public'),
        ]);
    }

    $this->reset('files');
}

Pattern 2: Stage-then-submit (recommended default)

Treat the dropzone as a form field, not an uploader. Files sit in Livewire's temp directory until the user hits Save. Benefits: the user can correct mistakes, you validate once, and a half-finished form doesn't litter storage. This is the right choice for 90% of forms.

Pattern 3: Upload-on-drop with updatedFiles()

When the UX demands immediate persistence (a media manager, a chat attachment), hook the property update:

public function updatedFiles()
{
    $this->validate();

    foreach ($this->files as $file) {
        Media::create([
            'disk' => 'public',
            'path' => $file->store('media', 'public'),
        ]);
    }

    $this->reset('files');
}

Don't mix this with a submit-time save() that also iterates $files, or you'll double-insert. Pick one ownership model per screen.

Pattern 4: Editing an existing attachment (added in 2.0.3)

Model it as "new upload wins, otherwise keep existing":

public $photo = null;          // new upload
public $existingPhoto;         // current path from the DB

public function save()
{
    $this->validateOnly('photo'); // nullable on edit screens

    $path = $this->photo
        ? $this->photo->store('profile-photos', 'public')
        : $this->existingPhoto;

    auth()->user()->update(['photo' => $path]);
    $this->reset('photo');
}

Keep the required rule off the edit form, or the user is forced to re-upload just to change their name.

Pattern 5: Multiple dropzones on one page

The component is unique by default (since 1.0.2) and UUID dispatch was fixed in 1.0.9, but you should still give each instance a stable identity when you hand-write markup or duplicate a Blade partial:

<livewire:dropzone wire:model="cover"  :multiple="false" wire:key="dz-cover" />
<livewire:dropzone wire:model="gallery" :multiple="true"  wire:key="dz-gallery" />

If two dropzones ever appear to share files, the first thing to check is duplicated wire:key/name values, not the package.

Integration tips

  • Validate twice, deliberately. Pass :rules for instant feedback; keep $rules on the server. Never derive authorization from the :rules prop.
  • Store through disks, not public paths. $file->store('x', 's3') keeps you portable; hardcoded public_path() breaks in containers.
  • Heavy processing belongs in a job. Store the original, dispatch a queued resize/thumbnail job, and show a "processing" state. Doing Intervention work inline in save() will make the Livewire request time out on large images.
  • Bridge to form objects manually. There's no wire:model support for a FormObject file property; keep $files as a plain array on the component and move paths into the form object during save().
  • Use the upload lifecycle for UX. The component fires upload start/success/error feedback; wire those to your toast system rather than inventing your own progress bar.
  • Extend with the dz- classes. Style via the documented dz- prefixed selectors in your own stylesheet (see Gotchas on the styles pipeline).

Gotchas and Tips

The max: rule is kilobytes

'max:2048' is ~2MB, not 2048MB. This is the single most common bug report on file-upload packages. Also confirm PHP agrees:

upload_max_filesize = 12M
post_max_size = 12M

Livewire's own config/livewire.php (temporary_file_upload.rules, max_upload_time, directory, disk) can reject a file before your component ever sees it — and the failure surfaces as a generic validation error that doesn't match your $rules message.

Uploads go through PHP, not direct-to-S3

Everything transits Livewire's temporary upload endpoint. For multi-hundred-MB files this means memory limits, request timeouts, and a temp disk with enough space. If your product needs large uploads or presigned S3 URLs, this package is the wrong tool (or you need its chunking support — verify against the docs for your version). Keep the dropzone for images and documents in the low tens of MB.

Don't manually delete temporary files

Since 1.0.6 the remove event handles cleanup. If you add your own removal handler and also call unlink()/Storage::delete() on the temp path, you'll race Livewire's cleanup. Let the component own removal; you only own the persisted file, and even then, delete the DB record first and the file in a deleted observer/job.

Version-major differences will bite you

  • v1 → v2.0.0: the built-in label was removed. If you upgrade and your form suddenly has no visible prompt, that's why — build your own heading/help text around the component.
  • v3.0.0: primarily dev-dependency fixes; behaviorally close to late v2, but keep ^3.0 pinned and re-test.
  • Livewire 4 support landed in the 2.x line (2.0.8). If you're on Livewire 3, stay on a version whose changelog covers your Livewire constraint.

Read CHANGELOG.md before every minor bump; this package's releases are mostly dependency bumps, so behavior changes hide in the majors.

Styling lives in a separate pipeline

The styles ship via a dedicated JS/PostCSS resources package (resources/js/livewire-dropzone-styles) and historically had a dz- class prefix fix (1.0.1). Consequences:

  • If the dropzone renders unstyled, the asset pipeline/publish step is the problem — verify the styles are actually included in your build, not that the component is broken.
  • Override in your own CSS using the dz- selectors. Don't edit vendor files; you'll lose it on composer update.
  • Themes are a paid/feedback-driven offering — plan on writing your own CSS if you need brand-matched visuals.

Client rules are not validation

Always re-validate server-side, and validate files.* (not just files) for arrays. Also authorize: a dropzone in a Blade view is not an access check. Confirm the user may attach to this record before storing.

Testing strategy

Don't try to test drag-and-drop in PHP. Test the server contract:

Livewire::test(ProfilePhoto::class)
    ->set('photo', UploadedFile::fake()->image('avatar.jpg'))
    ->call('save')
    ->assertHasNoErrors();

Storage::fake('public');

Use UploadedFile::fake()->create('doc.pdf', 5000) to exercise the size rule. For the JS side, assert in the browser layer (Dusk/Playwright) only if the drag interaction itself is a product requirement.

Accessibility and mobile

Drag-and-drop is the headline feature but never the only path — confirm click-to-browse works, keep a visible label/instructions (remember v2 removed the built-in label), associate @error output near the dropzone, and test on touch devices where drag events don't exist. Long filenames were fixed to wrap in 2.0.4, but very long names in your own surrounding UI still need break-words.

Debugging checklist

  1. Upload fails with no matching $rules error → check livewire.temporary_file_upload.rules and PHP post_max_size.
  2. Property stays null after dropping → check the wire:model name matches the public property exactly, and that the property is initialized.
  3. Two dropzones interfering → unique wire:key/name; you're on ≥1.0.9.
  4. File appears dropped but save() sees an empty array → don't add wire:model.defer/.live modifiers assuming they help; the component decides when it syncs.
  5. Unstyled widget → asset/styles publish step, not the PHP component.
  6. Orphaned files in storage → your persistence path is duplicated between updatedFiles() and save().
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.
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
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource