Gratis update & dukungan instalasi

Tutorial Website 19 Oct 2026

Cara Upload File di Laravel: Panduan Lengkap Storage, Validasi Keamanan & Trik Shared Hosting (2026)

R Oleh renz mobellgnd
Cara Upload File di Laravel: Panduan Lengkap Storage, Validasi Keamanan & Trik Shared Hosting (2026)

Cara Upload File di Laravel: Panduan Lengkap Storage, Validasi Keamanan & Trik Shared Hosting (2026)

Upload file di Laravel dilakukan dengan form ber-atribut enctype="multipart/form-data", validasi tipe dan ukuran file di controller, lalu penyimpanan lewat $request->file('dokumen')->store('uploads', 'public'). File tersimpan di storage/app/public dan bisa diakses publik lewat URL /storage/... setelah perintah php artisan storage:link dijalankan. Panduan ini membahas alurnya lengkap — dari konsep Storage & Disk, validasi yang aman dari file berbahaya, perbedaan disk publik dan privat, sampai trik agar gambar tidak error 404 saat di-deploy ke shared hosting.

Apa Itu Storage dan Disk di Laravel?

Laravel tidak menyimpan file upload langsung ke folder public/ seperti PHP native. Ia memakai abstraksi Filesystem yang dikonfigurasi di config/filesystems.php. Konsep kuncinya adalah disk: sebuah "tujuan penyimpanan" bernama yang bisa menunjuk ke folder lokal, ke Amazon S3, atau ke layanan lain.

Tiga disk bawaan yang perlu kamu kenal:

Disk Lokasi fisik Bisa diakses browser? Kegunaan
localstorage/app/private (Laravel 11+)TidakFile privat: invoice internal, dokumen rahasia
publicstorage/app/publicYa, lewat symlink public/storageFoto profil, gambar produk, lampiran publik
s3Bucket Amazon S3Tergantung konfigurasiSkala besar / multi-server

Keuntungan pola ini: kode upload kamu tidak perlu ditulis ulang saat infrastruktur berganti. Hari ini pakai disk lokal, besok pindah ke S3 — yang berubah hanya konfigurasi, bukan kode. Dokumentasi resmi ada di laravel.com/docs/filesystem.

Bagaimana Cara Upload File di Laravel? (6 Langkah)

Berikut alur standar yang dipakai hampir semua aplikasi Laravel, dari form sampai file tampil di browser.

Langkah 1 — Buat form dengan enctype. Tanpa atribut ini, file tidak akan terkirim sama sekali — ini kesalahan paling umum bagi pemula.

<form action="/profil/avatar" method="POST" enctype="multipart/form-data">
@csrf
<input type="file" name="avatar" accept="image/*">
<button type="submit">Upload</button>
</form>

Langkah 2 — Buat route dan controller.

// routes/web.php
use App\Http\Controllers\ProfilController;

Route::post('/profil/avatar', [ProfilController::class, 'updateAvatar']);

Langkah 3 — Validasi di controller (jangan dilewati). Validasi adalah garis pertahanan pertama; detail lengkapnya ada di section berikutnya.

public function updateAvatar(Request $request)
{
$request->validate([
'avatar' => 'required|image|mimes:jpg,jpeg,png,webp|max:2048',
]);

$path = $request->file('avatar')->store('avatars', 'public');

$request->user()->update(['avatar_path' => $path]);

return back()->with('sukses', 'Foto profil berhasil diupload.');
}

Method store() melakukan tiga hal sekaligus: membuat nama file unik (hash acak, bukan nama asli dari user), menyimpannya ke disk public dalam folder avatars/, dan mengembalikan path relatif seperti avatars/a8f3c2...jpg.

Langkah 4 — Jalankan storage:link sekali. Folder storage/ tidak bisa diakses browser secara langsung, jadi Laravel menyediakan symlink public/storage yang menunjuk ke storage/app/public:

php artisan storage:link

Langkah 5 — Simpan path di database. Simpan path relatif (avatars/xxx.jpg), bukan URL penuh. URL penuh membuat repot saat domain berubah.

Langkah 6 — Tampilkan file di Blade.

<img src="{{ asset('storage/' . $user->avatar_path) }}" alt="Foto profil">

Bagaimana Cara Memvalidasi Upload File dengan Aman?

Form upload adalah salah satu pintu masuk serangan paling klasik: penyerang bisa menyamarkan file PHP berbahaya menjadi foto.jpg, atau mengupload file raksasa untuk menghabiskan disk server. Berikut rule validasi Laravel yang wajib dipakai, dari dokumentasi validasi resmi:

Rule Fungsi Contoh
fileMemastikan input benar-benar file upload valid'dokumen' => 'file'
imageMemastikan file benar-benar gambar (cek konten, bukan ekstensi)'avatar' => 'image'
mimes:jpg,png,pdfMembatasi ekstensi yang diizinkan'lampiran' => 'mimes:pdf,docx'
mimetypes:image/jpegMembatasi tipe MIME aktual fileLapisan tambahan selain mimes
max:2048Batas ukuran dalam kilobyte (2048 = 2 MB)'avatar' => 'max:2048'
dimensionsBatas dimensi gambar'banner' => 'dimensions:min_width=1200,ratio=16/9'

Tiga prinsip keamanan yang sering dilupakan tutorial lain:

  1. Jangan pernah percaya nama file dari user. Nama asli bisa mengandung pola path traversal (../../) atau menimpa file penting. Laravel store() sudah memakai nama hash acak secara default — pertahankan perilaku itu. Kalau butuh nama asli untuk ditampilkan, simpan di kolom database terpisah.
  2. Validasi ganda: mimes + mimetypes. Penyerang bisa me-rename shell.php menjadi shell.jpg. Rule image dan mimetypes memeriksa isi file yang sebenarnya, bukan sekadar ekstensinya.
  3. Batasi ukuran di dua lapis. Validasi Laravel hanya jalan kalau file lolos batas PHP dulu. Kalau upload_max_filesize di php.ini hanya 2M, file 10 MB akan gagal sebelum sampai ke controller — tanpa pesan error yang jelas. Selaraskan upload_max_filesize dan post_max_size dengan kebutuhan aplikasimu. Upload yang aman adalah bagian dari checklist mengamankan website setelah beli source code.

Disk Publik vs Privat: Kapan Memakai yang Mana?

Tidak semua file boleh diakses siapa pun. Foto KTP user atau invoice internal tidak boleh bisa dibuka lewat URL tebakan. Aturannya sederhana:

  1. Disk public → untuk file yang memang untuk konsumsi publik: foto produk, avatar, gambar banner, lampiran artikel. Diakses langsung lewat /storage/....
  2. Disk local (privat) → untuk file yang butuh otorisasi: diserve lewat controller yang mengecek kepemilikan dulu, baru mengembalikan file.

Contoh pola file privat yang benar:

// File disimpan ke disk 'local' (tidak bisa diakses langsung)
$path = $request->file('ktp')->store('dokumen-ktp', 'local');

// Route download dengan pengecekan kepemilikan
Route::get('/dokumen/{id}', function (string $id) {
$dokumen = Dokumen::findOrFail($id);

abort_unless($dokumen->user_id === auth()->id(), 403);

return Storage::disk('local')->download($dokumen->path);
})->middleware('auth');

Pola yang sama dipakai saat kamu membangun REST API untuk aplikasi mobile: endpoint upload menerima multipart, menyimpan ke disk yang tepat, dan mengembalikan path/URL di respons JSON.

Bagaimana Mengatasi Gambar Tidak Tampil Setelah Deploy ke Shared Hosting?

Ini keluhan paling sering di forum: di localhost gambar tampil, setelah di-upload ke shared hosting malah 404. Penyebabnya hampir selalu satu: symlink public/storage belum dibuat, karena shared hosting umumnya tidak menyediakan akses SSH untuk menjalankan php artisan storage:link.

Tiga solusi praktis, dari yang paling direkomendasikan:

Opsi 1 — Jalankan artisan lewat route sementara. Buat route sekali pakai, buka di browser, lalu hapus route-nya:

// routes/web.php — HAPUS setelah berhasil dibuka sekali
Route::get('/buat-storage-link', function () {
Artisan::call('storage:link');
return 'Symlink dibuat.';
});

Opsi 2 — Cron job cPanel satu kali. Di menu Cron Jobs cPanel, jalankan perintah PHP artisan sekali saja (lalu hapus cron-nya):

/usr/local/bin/php /home/username/laravel/artisan storage:link

Opsi 3 — Disk khusus yang menunjuk langsung ke public_path. Kalau hosting memblokir symlink, daftarkan disk baru di config/filesystems.php yang menyimpan langsung ke folder publik:

'public_direct' => [
'driver' => 'local',
'root' => public_path('uploads'),
'url' => env('APP_URL') . '/uploads',
'visibility' => 'public',
],

Kekurangannya: kamu kehilangan kemudahan pindah ke S3 nanti dan pemisahan file publik/privat. Pakai opsi 1 atau 2 dulu; opsi 3 adalah jalan terakhir. Masalah symlink yang gagal juga sering berujung pada error 500 setelah upload website kalau permission folder tidak tepat — pastikan folder storage/ writable oleh web server.

Bagaimana Cara Menghapus File Lama Saat Diganti?

Aplikasi yang tidak membersihkan file lama akan menumpuk sampah di server sampai disk penuh. Pola standarnya: hapus file lama setelah file baru berhasil disimpan.

public function updateAvatar(Request $request)
{
$request->validate([
'avatar' => 'required|image|mimes:jpg,jpeg,png,webp|max:2048',
]);

$user = $request->user();
$lama = $user->avatar_path;

$baru = $request->file('avatar')->store('avatars', 'public');
$user->update(['avatar_path' => $baru]);

if ($lama && Storage::disk('public')->exists($lama)) {
Storage::disk('public')->delete($lama);
}

return back()->with('sukses', 'Foto profil diperbarui.');
}

Catatan: file upload juga wajib masuk ke strategi backup otomatis database & file — database saja tidak cukup, karena file fisiknya tinggal di storage/, bukan di tabel.

Kapan Cara Ini Tidak Cocok?

Jujur soal batasan: pola store() standar di atas tidak cocok untuk semua kasus.

  1. File sangat besar (ratusan MB ke atas). Upload sekaligus rawan timeout dan gagal di tengah jalan. Butuh upload bertahap (chunked) dengan library khusus atau langsung ke S3 via presigned URL.
  2. Butuh pemindaian virus. Validasi mimes/mimetypes hanya memeriksa tipe file, bukan isinya. Kalau aplikasimu menerima file dari publik tanpa batas (misal portal lamaran kerja), pertimbangkan pemindai antivirus server-side seperti ClamAV sebelum file disimpan permanen.
  3. File yang sering berubah-ubah nama. Path yang disimpan di database adalah kontrak; kalau kamu me-rename file manual di server, semua URL-nya rusak.

FAQ

Di mana Laravel menyimpan file yang diupload? Secara default ke storage/app/public bila memakai disk public, atau storage/app/private untuk disk local (Laravel 11+). Folder storage/ tidak bisa diakses browser langsung — symlink public/storage (dari php artisan storage:link) yang menjembatani akses publik.

Kenapa gambar hasil upload tidak tampil (404)? Penyebab paling umum: symlink public/storage belum dibuat. Di localhost jalankan php artisan storage:link; di shared hosting gunakan trik route artisan sementara seperti dijelaskan di atas.

Apa arti max:2048 pada validasi file? 2048 kilobyte = 2 MB. Satuannya kilobyte, bukan megabyte. Untuk batas 10 MB, tulis max:10240.

Apakah aman menyimpan file langsung ke folder public/? Bisa, tapi kamu kehilangan nama unik otomatis, kemudahan ganti driver ke S3, dan pemisahan file publik vs privat. Pakai Storage API kecuali ada alasan kuat.

Bagaimana cara membatasi hanya file PDF yang boleh diupload? Gunakan 'dokumen' => 'required|file|mimes:pdf|max:5120' — mimes:pdf membatasi ekstensi dan file memastikan input berupa upload valid. Tambahkan mimetypes:application/pdf untuk lapisan keamanan ekstra.

Apakah nama file asli dari user aman disimpan? Tidak untuk dijadikan nama file fisik — bisa mengandung path traversal atau menimpa file lain. Biarkan Laravel membuat nama hash acak via store(), dan simpan nama asli di kolom database terpisah hanya untuk ditampilkan.

Kesimpulan

Alur upload file di Laravel selalu sama: form dengan enctype, validasi ketat di controller, simpan lewat store() ke disk yang tepat, simpan path relatif di database, tampilkan lewat /storage/... setelah storage:link, dan hapus file lama saat ada pengganti. Kuasai pola ini sekali — termasuk beda disk publik vs privat dan trik symlink di shared hosting — dan kamu bisa menerapkannya ke semua variasi: foto profil, galeri produk, lampiran dokumen, sampai migrasi ke S3 nanti tanpa menulis ulang kode.

Butuh fondasi yang sudah jadi? Di kioskoding.com tersedia source code aplikasi Laravel siap pakai — termasuk fitur upload file, galeri, dan manajemen dokumen — yang tinggal kamu kembangkan lebih lanjut.