axn/livewire-upload-handler
Installation:
composer require axn/livewire-upload-handler
Ensure your project meets requirements (PHP 8.4+, Laravel 12+, Livewire 3.1+).
Layout Integration:
Add to your Blade layout (preferably app.blade.php):
<head>
@livewireStyles
@livewireUploadHandlerStyles
</head>
<body>
@livewireScripts
@livewireUploadHandlerScripts
</body>
Git Configuration: Run the provided one-liner to exclude temporary files:
# Add livewire-tmp/ to storage/app/.gitignore
if ! grep -q "livewire-tmp/" storage/app/.gitignore 2>/dev/null; then
echo "livewire-tmp/" >> storage/app/.gitignore
fi
First Upload Component: Use the simplest component for a single file upload:
<livewire:upload-handler.item />
This renders a drag-and-drop upload area with basic validation.
Create a Livewire Component:
php artisan make:livewire FileUploadDemo
Update the component to use the upload handler:
// app/Livewire/FileUploadDemo.php
public function mount()
{
$this->uploadHandler = new \AXN\LivewireUploadHandler\UploadHandler();
}
public function render()
{
return view('livewire.file-upload-demo');
}
Blade View:
<livewire:file-upload-demo />
@push('scripts')
@livewireScripts
@livewireUploadHandlerScripts
@endpush
Handle Uploads:
Extend the UploadHandler to process files:
use AXN\LivewireUploadHandler\Events\FileUploaded;
protected function handleFileUploaded(FileUploaded $event)
{
// Save to Spatie Media Library or custom logic
$this->media()->addMedia($event->filePath)->toMediaCollection('uploads');
}
$this->uploadHandler->chunkSize = 5 * 1024 * 1024; // 5MB chunks
progress property to display upload progress:
<progress value="{{ $uploadHandler->progress }}" max="100"></progress>
storage/app/livewire-tmp/.$this->uploadHandler->glideCachePath = storage_path('app/.livewire-upload-handler-glide-cache');
@if($uploadHandler->hasFiles())
@foreach($uploadHandler->files as $file)
<img src="{{ $uploadHandler->getPreviewUrl($file) }}" alt="{{ $file->name }}">
@endforeach
@endif
$this->uploadHandler->autoSave = true;
$this->uploadHandler->mediaCollection = 'user_avatars';
FileUploaded event and handle manually:
public function boot()
{
FileUploaded::listen(function (FileUploaded $event) {
$this->media()->addMedia($event->filePath)->toMediaCollection('documents');
});
}
<livewire:upload-handler.item
drop-zone-class="bg-blue-50 p-8 rounded-lg border-2 border-dashed border-blue-300"
drop-zone-text="Drag & drop files here or click to browse"
/>
$this->uploadHandler->sortable = true;
Update Blade to include Sortable.js:
@push('scripts')
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
<script>
document.addEventListener('livewire:init', () => {
Sortable.create(document.querySelector('.sortable-files'));
});
</script>
@endpush
UploadHandler to add validation:
$this->uploadHandler->addValidationRule('max_size', 10 * 1024 * 1024); // 10MB
$this->uploadHandler->addValidationRule('allowed_types', ['image/jpeg', 'image/png', 'application/pdf']);
$this->uploadHandler->validationRules = [
'file.*' => ['required', 'max:10240', function ($attribute, $value, $fail) {
if (!in_array($value->getClientOriginalExtension(), ['jpg', 'png'])) {
$fail('Only JPG and PNG files are allowed.');
}
}],
];
<livewire:upload-handler.item
upload-button-class="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded"
file-item-class="bg-gray-100 p-2 mb-2 rounded"
/>
$this->uploadHandler->setLocale('fr'); // French
php artisan vendor:publish --tag=livewire-upload-handler-translations
Then extend resources/lang/vendor/livewire-upload-handler/.boot() method:
public function boot()
{
\AXN\LivewireUploadHandler\Events\FileUploaded::listen(function ($event) {
Log::info('File uploaded: ' . $event->fileName);
});
\AXN\LivewireUploadHandler\Events\UploadFailed::listen(function ($event) {
session()->flash('error', 'Upload failed: ' . $event->error);
});
}
batch property to group uploads:
$this->uploadHandler->batch = 'user_documents_' . auth()->id();
@if($uploadHandler->hasFiles())
<button wire:click="deleteSelected" class="btn-danger">
Delete Selected
</button>
@endif
public function deleteSelected()
{
$this->uploadHandler->deleteSelected($this->selectedFiles);
}
livewire-tmp/ directory for incomplete chunks. Ensure:
chmod -R 775 storage/app/livewire-tmp).upload_max_filesize and post_max_size in php.ini are larger than your chunk size.UploadHandler:
$this->uploadHandler->debug = true;
Check Laravel logs for chunk-related errors.How can I help you explore Laravel packages today?