Laravel Filesystem Storage Disks File Uploads Signed Urls
Definisi
Laravel menyediakan abstraksi filesystem lewat Storage. Intinya, Laravel memisahkan cara aplikasi menyimpan file dari lokasi fisik penyimpanan.
Kamu bisa menulis kode yang sama untuk:
- local disk di server aplikasi
- public disk untuk aset yang bisa diakses browser
- cloud disk seperti S3, MinIO, atau provider object storage lain
Dengan abstraksi ini, controller dan service kamu tidak perlu tahu file disimpan di folder lokal, bucket cloud, atau storage yang dienkripsi.
Kapan memakai storage disk
Pakai storage disk kalau file tidak cukup disimpan sebagai string di database, atau kalau kamu butuh perilaku storage yang berbeda berdasarkan jenis file.
Gunakan disk untuk:
- upload user: avatar, lampiran, dokumen, gambar produk
- file yang harus bertahan lama dan bisa di-serve ulang
- file yang perlu dipisah berdasarkan environment
- file yang aksesnya perlu diatur, misalnya private download atau signed access
- aset yang nanti mungkin dipindah dari local ke cloud tanpa banyak ubah kode
Jangan pakai disk sebagai solusi utama kalau:
- file sangat kecil dan cuma perlu satu-dua value yang memang lebih cocok disimpan sebagai string atau JSON
- file sifatnya ephemeral, misalnya cache sementara yang tidak perlu persistence
- file hanya dipakai internal sesaat dan lebih aman dibuang setelah request selesai
Prinsip praktis
- Database simpan metadata dan relasi.
- Storage disk simpan binary file-nya.
- URL atau path simpan referensi ke file, bukan isi file.
Cara kerja storage disk
Storage disk adalah konfigurasi target penyimpanan.
Di Laravel, disk biasanya didefinisikan di config/filesystems.php, misalnya:
localuntuk storage lokal non-publicpublicuntuk file yang dibuka via web servers3untuk object storage
Yang penting bukan nama disk-nya, tapi tujuan dan policy aksesnya.
Contoh penggunaan
use Illuminate\Support\Facades\Storage;
Storage::disk('public')->put('avatars/user-1.jpg', $contents);
Storage::disk('s3')->put('reports/2026/q1.pdf', $contents);Kalau kebutuhan akses berubah, kamu biasanya cukup ganti disk atau konfigurasi, bukan refactor seluruh alur upload.
Alur upload → store → serve → signed access
Upload aman
Upload aman bukan cuma soal menerima file, tapi juga soal membatasi tipe, ukuran, lokasi simpan, dan cara aksesnya.
1. Validasi file secara eksplisit
Gunakan validasi Laravel seperti:
$request->validate([
'file' => ['required', 'file', 'max:5120', 'mimes:jpg,jpeg,png,pdf'],
]);Poin penting:
filememastikan input benar-benar file uploadmaxmembatasi ukuranmimesataumimetypesmembatasi jenis file
Jangan mengandalkan ekstensi file saja. Ekstensi bisa menipu.
2. Jangan percaya nama file dari user
Nama asli file bisa berisi spasi, karakter aneh, path traversal, atau konflik nama.
Lebih aman kalau kamu:
- generate nama file sendiri
- simpan nama asli hanya sebagai metadata kalau memang perlu
- normalisasi ekstensi jika diperlukan
Contoh:
$path = $request->file('avatar')->storePublicly('avatars', 'public');Atau kalau ingin kontrol nama:
$file = $request->file('avatar');
$name = $file->hashName();
$path = $file->storeAs('avatars', $name, 'public');3. Pisahkan file public dan private
Kalau file boleh dibuka langsung oleh browser, taruh di disk yang memang didesain untuk public access.
Kalau file sensitif, jangan taruh di public disk. Simpan di private disk lalu layani lewat controller atau signed URL.
4. Simpan metadata di database
Biasanya yang disimpan di database bukan file-nya, tapi:
- path
- original name
- mime type
- size
- owner/user id
- visibility
- checksum jika perlu
Ini memudahkan audit, reprocess, dan cleanup.
5. Bersihkan file lama
Kalau user mengganti avatar atau menghapus dokumen, file lama harus ikut dihapus kalau memang tidak dipakai lagi.
Kalau tidak, storage akan terus membengkak.
Contoh implementasi upload
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
public function store(Request $request)
{
$validated = $request->validate([
'document' => ['required', 'file', 'max:10240', 'mimes:pdf,doc,docx'],
]);
$file = $validated['document'];
$path = $file->store('documents', 'private');
// simpan $path ke database
return response()->json([
'path' => $path,
]);
}Untuk file yang memang harus bisa diunduh publik, gunakan disk dan folder yang tepat, bukan sekadar store() lalu berharap URL-nya bisa dibuka.
Serve file
Ada dua pola utama:
1. Serve langsung dari public disk
Cocok untuk:
- gambar publik
- CSS/asset generated
- file yang memang tidak sensitif
Contoh:
$url = Storage::disk('public')->url($path);Tapi pola ini hanya aman kalau file tersebut memang boleh diakses siapa saja.
2. Serve lewat controller
Cocok untuk:
- file private
- file yang perlu authorization
- file yang perlu logging akses
- file yang harus diberi header khusus
Contoh:
public function download(string $path)
{
abort_unless(auth()->user()->can('download-reports'), 403);
return Storage::disk('private')->download($path);
}Pola ini memberi kontrol penuh, tapi kamu harus memikirkan otorisasi, streaming, dan caching sendiri.
Signed URLs
Signed URL dipakai untuk memberi akses file atau route untuk waktu terbatas tanpa membuka resource secara publik selamanya.
Di Laravel ada dua model umum:
- temporary signed route untuk URL ke route/controller
- temporary URL dari storage disk untuk disk yang mendukungnya, seperti S3
Kapan dipakai
Pakai signed URL kalau:
- file private perlu diakses browser tanpa login ulang
- akses hanya valid beberapa menit
- kamu ingin link yang tidak mudah ditebak atau dibagikan selamanya
- backend perlu tetap memegang kontrol akses
Temporary signed route
use Illuminate\Support\Facades\URL;
$url = URL::temporarySignedRoute(
'documents.download',
now()->addMinutes(15),
['document' => $document->id]
);Lalu route-nya divalidasi dengan middleware signed:
Route::get('/documents/{document}/download', [DocumentController::class, 'download'])
->name('documents.download')
->middleware('signed');Kalau signature atau expiry tidak valid, Laravel akan menolak request.
Temporary URL dari storage
Untuk storage driver yang mendukung temporary URL:
$url = Storage::disk('s3')->temporaryUrl(
$path,
now()->addMinutes(10)
);Ini cocok untuk private object storage yang sudah punya mekanisme signed access di level storage.
Perbedaan signed route vs temporary storage URL
Signed route
- cocok untuk business logic access control
- bisa cek user, ownership, role, atau policy sebelum serve file
- fleksibel untuk local/private disk
Temporary storage URL
- cocok untuk object storage yang mendukung pre-signed URL
- request bisa langsung ke storage provider
- biasanya lebih efisien untuk file besar
Kalau kamu butuh kontrol otorisasi yang kompleks, signed route biasanya lebih mudah dirawat.
Kalau kamu butuh download langsung dari bucket, temporary storage URL sering lebih pas.
Catatan implementasi produksi
Gunakan visibility dengan sadar
Di Laravel, visibility bukan detail kosmetik. Itu menentukan siapa yang bisa baca file.
publicberarti file memang boleh diakses browserprivateberarti file harus lewat mekanisme kontrol akses
Simpan path yang stabil
Kalau file dipindah atau rename di storage, pastikan database ikut diperbarui.
Jangan hardcode URL final di banyak tempat. Simpan path logis dan generate URL saat dibutuhkan.
Perhatikan environment parity
Local disk, symlink public, dan S3 sering kelihatan mirip di development, tapi perilakunya beda di production.
Contoh jebakan:
- file lokal jalan karena
public/storage, tapi bucket cloud butuh konfigurasi lain - URL public lokal berhasil, tetapi file private di production justru kebuka tanpa sengaja
- MIME type atau header download tidak konsisten antar driver
Tangani ukuran file besar dengan benar
Jangan baca file besar ke memory tanpa alasan.
Untuk file besar, gunakan streaming atau response download dari storage.
Cleanup itu bagian dari fitur
Production storage sering bocor bukan karena upload gagal, tapi karena delete dan replace tidak membersihkan file lama.
Buat aturan:
- kapan file lama dihapus
- siapa yang berhak menghapus
- apakah ada soft-delete file
- apakah ada job cleanup berkala
Jebakan umum saat handling file di production
1. Menganggap file upload aman hanya karena ekstensi benar
Ekstensi .jpg tidak otomatis berarti file itu aman. Selalu validasi dari sisi Laravel dan, kalau perlu, cek MIME/content.
2. Menyimpan file sensitif di public disk
Begitu file ada di public storage, siapa pun yang tahu URL bisa mencoba akses.
3. Tidak membatasi ukuran upload
Upload besar bisa memakan bandwidth, disk, dan waktu proses. Batasi di aplikasi dan di server.
4. Tidak menangani nama file duplikat
Nama file yang sama dari banyak user bisa saling timpa kalau naming strategy buruk.
5. Tidak memisahkan metadata dan binary file
Kalau database dipakai untuk menyimpan isi file, backup dan query jadi berat. Simpan binary di storage, metadata di database.
6. Lupa hapus file lama
Ini jebakan yang paling sering muncul saat edit profile, ganti dokumen, atau regenerate asset.
7. Signed URL terlalu lama
Expiry yang terlalu panjang mengurangi nilai signed access. Kalau file sensitif, pilih expiry singkat dan policy yang jelas.
8. Mengandalkan local behavior untuk production
Jangan cek berhasil hanya di local. Pastikan flow juga jalan di storage target production.
Contoh pola yang sehat
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\URL;
public function show(Document $document)
{
abort_unless(auth()->id() === $document->user_id, 403);
$url = URL::temporarySignedRoute(
'documents.download',
now()->addMinutes(10),
['document' => $document->id]
);
return response()->json([
'download_url' => $url,
]);
}
public function download(Document $document)
{
abort_unless(request()->hasValidSignature(), 403);
abort_unless(auth()->id() === $document->user_id, 403);
return Storage::disk('private')->download($document->path, $document->original_name);
}Ringkasan keputusan cepat
- Pakai storage disk untuk memisahkan lokasi penyimpanan dari logika aplikasi.
- Pakai public disk hanya untuk file yang memang aman dibuka publik.
- Pakai private disk + controller untuk file sensitif.
- Pakai temporary signed route kalau butuh kontrol akses di Laravel.
- Pakai temporaryUrl kalau disk/object storage mendukung pre-signed access.
- Selalu validasi file upload, batasi ukuran, dan simpan metadata di database.
Referensi terkait
- Laravel Filesystem
- Laravel Files Upload
- Signed URLs dan temporary routes
- Storage driver configuration
- File cleanup strategy di production