Menangani file di Livewire: upload dengan trait WithFileUploads dan TemporaryUploadedFile, validasi dan batas ukuran, preview, progress bar dengan event Alpine, serta streaming downloads dan pembersihan temp files.

Setiap aplikasi nyata pada akhirnya membutuhkan file: avatar, lampiran dokumen, gambar produk, atau export data. Di Livewire, upload file terasa "gratis" — sebuah input dengan wire:model sudah cukup — tetapi ada lapisan penting yang perlu dipahami: bagaimana Livewire menyimpan file sementara sebelum disimpan permanen, bagaimana memvalidasi, dan bagaimana memberikan feedback berupa progress.
Inti yang perlu diingat sejak awal: upload Livewire tidak langsung menyimpan file. File di-upload ke area temp (livewire-tmp/), direpresentasikan sebagai TemporaryUploadedFile, lalu kalian yang memutuskan kapan menyimpannya secara permanen. Ini adalah fitur keamanan sekaligus sumber kebingungan bagi pemula.
Tambahkan trait WithFileUploads dan property untuk menampung file:
<?php
namespace App\Livewire;
use Livewire\WithFileUploads;
use Livewire\Component;
class UploadAvatar extends Component
{
use WithFileUploads;
public $avatar;
public function save()
{
$this->validate([
'avatar' => ['required', 'image', 'max:2048'],
]);
$path = $this->avatar->store('avatars', 'public');
// simpan $path ke user...
}
}<form wire:submit="save">
<input type="file" wire:model="avatar">
@error('avatar') <span>{{ $message }}</span> @enderror
<button type="submit">Simpan</button>
</form>Yang terjadi di balik layar:
livewire-tmp/) dan property $avatar berisi instance TemporaryUploadedFile.save() dipanggil, $this->avatar->store(...) memindahkan file ke lokasi permanen.Note
Nama method upload bersifat reserved di Livewire — jangan beri nama action upload(). Kesalahan ini menimbulkan error yang membingungkan. Gunakan save, store, submit, dst.
Aturan validasi file mengikuti sintaks Laravel biasa (image, mimes, max):
$this->validate([
'avatar' => ['required', 'image', 'mimes:jpg,jpeg,png,webp', 'max:2048'],
'documents.*' => ['file', 'mimes:pdf', 'max:10240'],
]);Selain aturan per-property, Livewire punya aturan default global untuk semua upload temp: file|max:12288 (12 MB). Kalian bisa ubah di config/livewire.php:
'temporary_file_upload' => [
'rules' => 'file|mimes:png,jpg,pdf|max:102400',
'directory' => 'livewire-tmp',
],Aturan global ini melindungi aplikasi dari upload raksasa bahkan sebelum kode kalian memvalidasi. Untuk project besar yang memakai S3, direktori temp dan chunking (upload dalam potongan) juga dikonfigurasi di sini.
Untuk gambar, temporaryUrl() menghasilkan URL preview sementara:
<div>
@if ($avatar)
<img src="{{ $avatar->temporaryUrl() }}">
@endif
<input type="file" wire:model="avatar">
</div>Untuk beberapa file sekaligus, tambahkan atribut multiple dan property array:
<input type="file" wire:model="documents" multiple>
@foreach ($documents as $file)
<p>{{ $file->getClientOriginalName() }}</p>
@endforeachSetiap item $file adalah TemporaryUploadedFile — panggil store() per item untuk menyimpannya permanen.
Livewire mengirim event livewire-upload-* selama upload. Digabungkan dengan Alpine (bundled), kita bisa membangun progress bar:
<div
x-data="{ progress: 0 }"
x-on:livewire-upload-start="progress = 0"
x-on:livewire-upload-finish="progress = 100"
x-on:livewire-upload-error="progress = 0"
x-on:livewire-upload-progress="progress = $event.detail.progress"
>
<input type="file" wire:model="video">
<div class="h-2 w-full bg-gray-200">
<div class="h-2 bg-blue-500" :style="`width: ${progress}%`"></div>
</div>
<span x-text="`${progress}%`"></span>
</div>$event.detail.progress berisi persentase (0-100). Ini adalah contoh paling umum kombinasi Livewire + Alpine — kita akan membedah pola ini lebih dalam di episode 15.
Untuk file besar, gunakan streamDownload agar file dialirkan langsung ke browser tanpa memuat seluruhnya di memory:
public function exportCsv()
{
$this->streamDownload(function () {
echo "id,judul\n";
foreach (Post::cursor() as $post) {
echo "{$post->id},{$post->title}\n";
}
}, 'posts.csv');
}streamDownload(callback, filename) menulis response secara bertahap — ideal untuk export data besar. Bandingkan dengan response()->download() biasa yang harus menyiapkan file utuh dulu di disk.
File temp di livewire-tmp/ otomatis dibersihkan oleh Livewire (file lebih dari sehari dihapus). Dua hal yang wajib diperhatikan:
TemporaryUploadedFile sebagai public property permanen — pindahkan ke storage permanen di action save/store. File temp bukan tempat penyimpanan data.max, mimes di validate() mencegah file berbahaya atau raksasa masuk ke storage permanen.Warning
File upload adalah permukaan serangan klasik. Selalu validasi mime dan ukuran, simpan dengan nama acak (store() melakukannya otomatis, jangan pakai nama asli user langsung), dan jangan pernah menyajikan file temp (livewire-tmp/) secara publik.
upload: reserved oleh Livewire, akan error.WithFileUploads: input file tidak akan terproses.TemporaryUploadedFile langsung tanpa store(): file akan hilang saat cleanup temp.temporaryUrl(): memakai path temp langsung tidak aman dan tidak berfungsi untuk disk non-lokal.mimes, max) tetap wajib.Inti yang harus dibawa pulang:
livewire-tmp/) → TemporaryUploadedFile → store() saat simpan.WithFileUploads; jangan beri nama action upload.config/livewire.php.livewire-upload-* + Alpine; preview via temporaryUrl().streamDownload(); file temp dibersihkan otomatis.Di episode 13 selanjutnya kita akan membahas authorization & security — policy dan ability check di action, conditional rendering dengan @can, serta penanganan CSRF/XSS, sanitization, dan binding protection di Livewire. Sampai jumpa di episode 13!