Memuat...
👋 Selamat Pagi!

Cara Upload File Gambar di Laravel dengan Validasi Aman

Tutorial lengkap upload image Laravel dengan validasi MIME type, ukuran file, dan tips keamanan server-side untuk junior dev dan mahasiswa IT.

Cara Upload File Gambar di Laravel dengan Validasi Aman

Upload gambar di Laravel itu mudah kalau kamu hanya mau "bisa jalan".

Tapi kalau mau aman, rapi, dan tahan serangan? Ceritanya berbeda.

Banyak tutorial Laravel yang mengajarkan $request->file('image')->store('uploads') lalu selesai. Padahal tanpa validasi yang benar, fitur upload bisa jadi celah masuknya file berbahaya ke server kamu.

Artikel ini akan memandu kamu dari nol — mulai konfigurasi filesystem, membuat form upload, validasi server-side, menyimpan file dengan aman, hingga tips mencegah eksploitasi. Cocok untuk mahasiswa IT dan junior developer yang ingin belajar Laravel dengan benar.

Persiapan: Konfigurasi Filesystem dan Storage di Laravel

Sebelum menulis satu baris kode pun, pastikan konfigurasi filesystem Laravel kamu sudah benar.

Buka file config/filesystems.php. Di sinilah Laravel mendefinisikan disk penyimpanan.

// config/filesystems.php
'disks' => [
    'local' => [
        'driver' => 'local',
        'root'   => storage_path('app'),
    ],

    'public' => [
        'driver'     => 'local',
        'root'       => storage_path('app/public'),
        'url'        => env('APP_URL') . '/storage',
        'visibility' => 'public',
    ],
],

Untuk menyimpan gambar yang bisa diakses publik, gunakan disk public.

Setelah itu, jalankan perintah berikut untuk membuat symlink dari public/storage ke storage/app/public:

php artisan storage:link

Perintah ini wajib dijalankan sekali di setiap environment (local dan production). Tanpanya, gambar yang kamu simpan tidak akan bisa diakses lewat URL.

Pastikan juga file .env kamu memiliki nilai APP_URL yang benar:

APP_URL=http://localhost:8000

Membuat Form Upload dengan Validasi MIME Type dan Ukuran File

Ini bagian paling krusial yang sering diabaikan developer pemula.

Membuat Form HTML

Buat view sederhana untuk form upload. Perhatikan atribut enctype — ini wajib ada agar file bisa dikirim:

<!-- resources/views/upload/form.blade.php -->
<form action="{{ route('upload.store') }}" method="POST" enctype="multipart/form-data">
    @csrf

    <div>
        <label for="image">Pilih Gambar</label>
        <input
            type="file"
            name="image"
            id="image"
            accept="image/jpeg,image/png,image/webp"
        >
        @error('image')
            <p style="color:red;">{{ $message }}</p>
        @enderror
    </div>

    <button type="submit">Upload Gambar</button>
</form>

Atribut accept di sisi client itu hanyalah UX helper — bukan keamanan. Validasi sesungguhnya harus terjadi di server.

Validasi di Controller

Buat controller dengan validasi yang ketat:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ImageUploadController extends Controller
{
    public function store(Request $request)
    {
        // Validasi server-side yang ketat
        $validated = $request->validate([
            'image' => [
                'required',
                'file',
                'image',                    // Memastikan file adalah gambar
                'mimes:jpeg,jpg,png,webp',  // Whitelist MIME type
                'max:2048',                 // Maksimal 2MB (dalam kilobyte)
                'dimensions:min_width=100,min_height=100,max_width=2000,max_height=2000',
            ],
        ]);

        // Lanjut ke proses penyimpanan...
    }
}

Penjelasan setiap rule validasi:

  • file — memastikan input benar-benar file, bukan string atau array
  • image — Laravel akan memeriksa apakah file adalah gambar valid menggunakan getimagesize()
  • mimes:jpeg,jpg,png,webp — whitelist ekstensi file yang diizinkan
  • max:2048 — batas ukuran file 2MB
  • dimensions — membatasi resolusi gambar agar tidak kehabisan memori saat diproses

Gunakan pesan error kustom agar lebih informatif bagi pengguna:

$messages = [
    'image.required'   => 'Gambar wajib diunggah.',
    'image.image'      => 'File yang diunggah harus berupa gambar.',
    'image.mimes'      => 'Format gambar harus JPEG, PNG, atau WebP.',
    'image.max'        => 'Ukuran gambar maksimal 2MB.',
    'image.dimensions' => 'Dimensi gambar tidak valid.',
];

$validated = $request->validate([
    'image' => ['required', 'file', 'image', 'mimes:jpeg,jpg,png,webp', 'max:2048'],
], $messages);

Menyimpan File dengan Nama Unik dan Struktur Folder Rapi

Jangan pernah menyimpan file dengan nama asli dari user. Ini adalah kesalahan keamanan klasik.

Nama file asli bisa mengandung karakter berbahaya, path traversal (../../etc/passwd), atau menimpa file yang sudah ada.

Menggunakan Nama Unik dengan UUID

use Illuminate\Support\Str;

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

    $file      = $request->file('image');
    $extension = $file->getClientOriginalExtension();

    // Generate nama unik dengan UUID
    $filename = Str::uuid() . '.' . strtolower($extension);

    // Struktur folder berdasarkan tahun/bulan
    $folder = 'images/' . date('Y/m');

    // Simpan ke disk public
    $path = $file->storeAs($folder, $filename, 'public');

    // Simpan path ke database
    // ImageModel::create(['path' => $path, 'user_id' => auth()->id()]);

    return redirect()->back()->with('success', 'Gambar berhasil diunggah.');
}

Struktur folder images/2026/07/ akan membuat penyimpanan tetap rapi meski file terus bertambah.

Alternatif: Menggunakan hashName()

Laravel menyediakan helper hashName() yang lebih ringkas:

$path = $request->file('image')->store('images/' . date('Y/m'), 'public');
// Hasilnya: images/2026/07/9f3a2b1c4d5e6f7a.jpg (nama hash otomatis)

Pilih antara UUID atau hashName sesuai kebutuhan — keduanya aman karena tidak menggunakan nama asli file.

Menampilkan Gambar dan Menangani Error Upload dengan Benar

Menampilkan Gambar dari Storage

Setelah path tersimpan di database, tampilkan gambar menggunakan helper Storage::url():

// Di Controller
use Illuminate\Support\Facades\Storage;

public function show($id)
{
    $image     = ImageModel::findOrFail($id);
    $imageUrl  = Storage::disk('public')->url($image->path);

    return view('upload.show', compact('imageUrl'));
}
<!-- Di Blade View -->
<img
    src="{{ Storage::disk('public')->url($image->path) }}"
    alt="Deskripsi gambar"
    loading="lazy"
>

Selalu gunakan Storage::url() daripada hardcode URL. Ini memastikan URL tetap benar di semua environment.

Menangani Kegagalan Upload

Bungkus proses penyimpanan dalam blok try-catch untuk menangani error secara graceful:

use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;

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

    try {
        $file     = $request->file('image');
        $filename = Str::uuid() . '.' . strtolower($file->getClientOriginalExtension());
        $path     = $file->storeAs('images/' . date('Y/m'), $filename, 'public');

        if (!$path) {
            throw new \Exception('Gagal menyimpan file ke storage.');
        }

        // Simpan ke database...

        return redirect()->back()->with('success', 'Gambar berhasil diunggah.');

    } catch (\Exception $e) {
        Log::error('Upload gagal: ' . $e->getMessage());
        return redirect()->back()->with('error', 'Upload gagal. Silakan coba lagi.');
    }
}

Jangan tampilkan pesan error teknis ke pengguna. Log saja di server, tampilkan pesan generik yang ramah pengguna.

Menghapus File Lama Saat Update

Kalau user mengganti foto profil, jangan lupa hapus file lama:

public function update(Request $request, $id)
{
    $request->validate([
        'image' => ['nullable', 'file', 'image', 'mimes:jpeg,jpg,png,webp', 'max:2048'],
    ]);

    $user = User::findOrFail($id);

    if ($request->hasFile('image')) {
        // Hapus file lama jika ada
        if ($user->avatar && Storage::disk('public')->exists($user->avatar)) {
            Storage::disk('public')->delete($user->avatar);
        }

        $filename     = Str::uuid() . '.' . $request->file('image')->getClientOriginalExtension();
        $path         = $request->file('image')->storeAs('avatars', $filename, 'public');
        $user->avatar = $path;
        $user->save();
    }

    return redirect()->back()->with('success', 'Profil diperbarui.');
}

Tips Keamanan: Mencegah Upload File Berbahaya di Aplikasi Laravel

Validasi MIME type saja tidak cukup. Penyerang bisa memanipulasi header file untuk menyamarkan PHP script sebagai gambar.

1. Jangan Simpan File di Folder Public yang Bisa Dieksekusi

Simpan selalu file di storage/app/public, bukan langsung di public/. Folder public/storage hanya berisi symlink — file asli ada di luar webroot dan tidak bisa dieksekusi langsung oleh server.

2. Verifikasi Konten File dengan finfo

Tambahkan pengecekan MIME type menggunakan finfo untuk validasi yang lebih dalam:

use Illuminate\Validation\Rules\File;

$request->validate([
    'image' => [
        'required',
        File::image()
            ->min(1)           // Minimal 1KB
            ->max(2 * 1024)    // Maksimal 2MB
            ->dimensions(Rule::dimensions()->maxWidth(2000)->maxHeight(2000)),
    ],
]);

Atau menggunakan pengecekan manual dengan finfo:

$file     = $request->file('image');
$finfo    = new \finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($file->getPathname());

$allowedMimes = ['image/jpeg', 'image/png', 'image/webp'];

if (!in_array($mimeType, $allowedMimes)) {
    return back()->withErrors(['image' => 'Tipe file tidak diizinkan.']);
}

3. Rename File dan Hapus Metadata EXIF

Nama unik yang kamu buat sudah mengatasi path traversal. Tapi gambar juga bisa mengandung metadata EXIF yang menyimpan lokasi GPS atau informasi sensitif lainnya.

Gunakan library seperti Intervention Image untuk membersihkan EXIF saat menyimpan:

composer require intervention/image
use Intervention\Image\Laravel\Facades\Image;

$image = Image::read($request->file('image'));
$image->scaleDown(width: 1200);  // Resize sekaligus

// Simpan tanpa metadata EXIF
$filename = Str::uuid() . '.jpg';
$path     = storage_path('app/public/images/' . date('Y/m') . '/' . $filename);

$image->toJpeg(quality: 85)->save($path);

4. Batasi Upload Hanya untuk User Terautentikasi

Pastikan route upload dilindungi middleware auth:

// routes/web.php
Route::middleware(['auth'])->group(function () {
    Route::get('/upload', [ImageUploadController::class, 'create'])->name('upload.create');
    Route::post('/upload', [ImageUploadController::class, 'store'])->name('upload.store');
});

5. Rate Limiting untuk Upload

Tambahkan throttle agar tidak bisa di-spam:

Route::middleware(['auth', 'throttle:10,1'])->group(function () {
    Route::post('/upload', [ImageUploadController::class, 'store'])->name('upload.store');
});

Artinya maksimal 10 request upload per menit per user.

Checklist Sebelum Deploy ke Production

Sebelum fitur upload kamu naik ke server production, pastikan semua poin ini sudah terpenuhi:

  • Validasi mimes dan image aktif di server-side
  • Batas ukuran file sesuai kebutuhan bisnis
  • File disimpan dengan nama UUID atau hash, bukan nama asli
  • php artisan storage:link sudah dijalankan di server
  • Folder penyimpanan di luar webroot atau non-executable
  • Route upload dilindungi middleware auth
  • Rate limiting aktif untuk mencegah spam upload
  • Error handling dengan try-catch dan logging
  • File lama dihapus saat user melakukan update

Fitur upload gambar yang benar bukan soal berapa baris kode yang kamu tulis — tapi soal berapa banyak skenario buruk yang kamu antisipasi sebelum terjadi.

Dengan validasi MIME type, nama file unik, dan penyimpanan di luar webroot, kamu sudah jauh lebih aman dari sebagian besar celah yang biasa dieksploitasi penyerang.

Kesulitan dengan tugas programming atau butuh bantuan coding? KerjaKode siap membantu menyelesaikan tugas IT dan teknik informatika kamu. Dapatkan bantuan profesional di jasa tugas IT KerjaKode.

Ajie Kusumadhany
Written by

Ajie Kusumadhany

Founder & Lead Developer KerjaKode. Berpengalaman dalam pengembangan web modern dengan Laravel, React.js, Vue.js, dan teknologi terkini. Passionate tentang coding, teknologi, dan berbagi pengetahuan melalui artikel.

Promo Spesial Hari Ini!

10% DISKON

Promo berakhir dalam:

00 Jam
:
00 Menit
:
00 Detik
Klaim Promo Sekarang!

*Promo berlaku untuk order hari ini

0
User Online
Halo! 👋
Kerjakode Support Online
×

👋 Hai! Pilih layanan yang kamu butuhkan:

Chat WhatsApp Sekarang