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 Upload Handler Laravel Package

axn/livewire-upload-handler

Modern file upload handler for Livewire 3 with chunked uploads, Glide image previews, drag and drop, sortable files, validation, themes, and Spatie Media Library integration. PHP 8.4 and Laravel 12.

View on GitHub
Deep Wiki
Context7

Getting Started

Treat this as a Livewire component library, not an SDK. You will not be calling an API — you will be dropping Blade tags into forms and, when your persistence model doesn't match the defaults, subclassing two classes. Day-to-day, 90% of your interaction is component props and Alpine event handlers.

Hard gate first. Before composer require, confirm the host app is on PHP 8.4+ / Laravel 12+ / Livewire 3.1+. The package uses asymmetric visibility, enums, and typed exceptions — it will not install on 8.3. If any service sharing that composer lock is pinned lower, this becomes a platform upgrade project, not a dependency bump. Resolve that before anything else.

composer require axn/livewire-upload-handler

Add both stacks to your layout — styles in <head>, scripts before </body>, in the same order as Livewire's own directives:

<head>
    @livewireStyles
    @livewireUploadHandlerStyles
</head>
<body>
    @livewireScripts
    @livewireUploadHandlerScripts
</body>

Then do the two pieces of housekeeping immediately, because discovering them later is annoying and confusing:

echo "livewire-tmp/" >> storage/app/.gitignore
mkdir -p storage/app/.livewire-upload-handler-glide-cache && \
echo "*" > storage/app/.livewire-upload-handler-glide-cache/.gitignore && \
echo "!.gitignore" >> storage/app/.livewire-upload-handler-glide-cache/.gitignore

Where to look first. docs/basic-usage.md for the props table, then the source of Axn\LivewireUploadHandler\Livewire\Item and Group. There are two parallel hierarchies you must understand before writing anything:

  • Group → Item — you implement storage yourself.
  • MediaGroup → MediaItem — Spatie Media Library auto-saves for you.

If your models already have media() relationships, start with the Media path. If files land in a bespoke table or a non-Spatie disk, start with Item and subclass.

First use case — a single avatar field, manual mode, no auto-save, no Media Library. Keep it boring; you are validating the asset pipeline, directive ordering, and gitignore hygiene in isolation, not the features:

<livewire:upload-handler.item
    inputBaseName="avatar"
    :acceptsMimeTypes="['image/jpeg', 'image/png']"
    :maxFileSize="2 * 1024 * 1024"
    :showImagePreview="true"
/>

Success criterion: upload, preview, cancel, re-upload all work in staging with production-sized files. Do this before you touch chunking or Spatie.

Implementation Patterns

Pattern 1 — Manual mode inside a form (the default). With autoSave off, the file sits in livewire-tmp and the component holds a tmpName that you save yourself on submit. This is the right pattern when file persistence must be transactional with the rest of the form.

<livewire:upload-handler.item inputBaseName="contract" :showFileSize="true" :maxFileSize="10 * 1024 * 1024" />

Pattern 2 — Multiple files with sortable ordering and Media Library auto-save. This is the package's sweet spot and the one that justifies the dependency:

<livewire:upload-handler.media-group
    :model="$project"
    mediaCollection="documents"
    :autoSave="true"
    :sortable="true"
    :maxFilesNumber="10"
/>

sortable writes order back through the group; deletions remove the Media row. Verify both across a reload before you build UI on top of it.

Pattern 3 — Subclass when storage is bespoke. When you outgrow the generic path, extend Item and implement three seams rather than fighting the component:

class DocumentItem extends \Axn\LivewireUploadHandler\Livewire\Item
{
    protected function saveUploadedFile(TemporaryUploadedFile $uploadedFile): void { /* ... */ }
    public function deleteSavedFile(): void { /* ... */ }
    protected function savedFileName(): string { return $this->document->filename; }
}

Pair it with a Group subclass overriding itemComponentClassName() and initialEntities(). Both Group and Item have a commonTraits() hook (added in 1.2.0) for shared config across item instances. If you're starting fresh, scaffold instead of hand-writing:

php artisan make:upload-handler Project/Document     # Group + Item + trait + views
php artisan make:upload-handler Project/Avatar --single

Pattern 4 — React in the UI via Alpine, not Livewire listeners. The package dispatches browser events (luh-uploaded, luh-canceled, luh-media-saved, luh-media-deleted) specifically so Alpine can catch them with x-on. Every payload carries inputBaseName so multiple uploaders on one page don't collide.

<div x-data="{ count: 0 }" x-on:luh-media-saved.window="count++">
    <livewire:upload-handler.media-group :model="$post" mediaCollection="images" :autoSave="true" />
</div>

Use luh-media-saved's mediaId to trigger refreshes or redirects, and luh-canceled for analytics/undo UX.

Pattern 5 — wire:model on the component. Since 1.2.0 you can bind the uploader into parent Livewire state, which is the cleanest way to coordinate it with "Save" buttons and dirty-form guards in a larger form.

Pattern 6 — Layer validation, don't trust it. Set acceptsMimeTypes, maxFileSize, and the image dimension props (minWidth/maxWidth/minHeight/maxHeight, added in 1.4.0) for UX, then re-enforce the same rules server-side in your save hook or form request. Client-side checks are not a security control.

Pattern 7 — Chunk sizing for large files. chunk_size is auto-derived from upload_max_filesize. For anything over a few tens of MB, walk the whole chain (nginx client_max_body_size, load balancer body limits, PHP-FPM post_max_size) and set chunk_size explicitly below the smallest limit in that chain.

Gotchas and Tips

Pin the exact version and read UPGRADE.md on every bump. This package has shipped breaking changes on minor and patch releases: 1.3.0 renamed all four public events (livewire-upload-handler:* → luh-*), 1.2.0 changed the extension API and flipped savedFileDisk to null by default, 1.1.0 renamed saveItemOrder → saveFileOrder. Treat each upgrade as a change request, not a composer update.

The 1.2.0 extension API rename is the one that bites. itemComponentParams() was removed in favour of commonTraits(), and saveItemOrder became saveFileOrder (now taking a file id, not itemId). If you find tutorials or older forks, they're stale.

Silent orphan temp files. showTemporaryFileWarning defaults to false since 1.2.0. Users will upload, navigate away, and leave files in livewire-tmp with no visual cue — and no package-level cleanup (no retention policy, no scheduled prune). Write your own scheduled task for storage/app/livewire-tmp and turn the warning on for user-facing forms.

Glide cache is per-node and nobody invalidates it. storage/app/.livewire-upload-handler-glide-cache is local disk by default. In a multi-node setup, either point it at shared storage or accept inconsistent previews — and be aware that deleting a media item doesn't purge derivatives. If you already run Cloudinary/imgix/Intervention, you now have a second image pipeline. Decide that explicitly.

Chunked uploads fail in ways that look like package bugs. Debug order: browser network tab (are chunks actually splitting?), storage/app/livewire-tmp (are chunks landing?), proxy body limits (are they being rejected before PHP?), then chunk_size config. Nine times out of ten it's nginx.

Theme drift. Defaults are bootstrap-5 + fontawesome-7. Overriding means publishing (vendor:publish --tag=livewire-upload-handler:themes) and owning the diff — package upgrades can change Blade structure underneath your overrides. Prefer shipping your own CSS on top of the default theme classes where possible.

Common runtime errors and their causes:

  • MediaCollectionNotRegisteredException — the mediaCollection string doesn't exist on that model's registerMediaCollections(). Check the model, not the component.
  • Empty previews — showImagePreview not set and glide_sign_key/glide_image_driver misconfigured. Try gd before imagick.
  • Nothing renders — the Blade stacks are missing or ordered after Livewire's own scripts.

Security and compliance gaps are yours to fill. The package does no AV scanning, quarantine, legal hold, transcoding, or audit logging. If your requirements include any of those, schedule them as a separate workstream — do not assume upload validation covers you.

Treat the maintainer as a named risk. Single corporate maintainer, 1 star, zero dependents, no SLA, no CVE process. That's fine for an internal pinned dependency if one engineer on the team can read the source — the extension seams are small and documented. Budget 2–5 engineer-days to establish a vendored-fork path and patch workflow before you need it, and put the quarterly "is upstream still alive?" review in the risk register rather than in someone's head.

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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
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
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core