# Laravel Image Upload: Secure File Handling and Validation Best Practices

> Master secure Laravel image uploads with best practices. Learn file validation, secure storage, and prevent common attacks for robust applications.

- Repository: [Laravel/framework](https://github.com/laravel/framework)
- Tags: best-practices
- Published: 2026-02-16

---

**Use Laravel's `File::image()` validation rules to whitelist MIME types and extensions, validate dimensions and size before storage, then use `UploadedFile::store()` with hashed filenames on a dedicated disk to prevent path traversal and spoofing attacks.**

Laravel image upload security relies on a three-layer defense implemented in the `laravel/framework` repository: strict validation via the `File` and `ImageFile` rule classes, safe storage through the `UploadedFile` API, and controlled access via filesystem disks. This guide examines the actual source code to show you how to handle user-uploaded images without introducing common vulnerabilities like MIME spoofing or path traversal.

## Validate Before Storage Using File and ImageFile Rules

Always validate the request before touching the filesystem. In [`src/Illuminate/Validation/Rules/File.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Validation/Rules/File.php), the `File` class provides a fluent API for defining acceptable uploads, while [`src/Illuminate/Validation/Rules/ImageFile.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Validation/Rules/ImageFile.php) handles image-specific constraints.

The recommended validation chain for a Laravel image upload looks like this:

```php
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\File;

$request->validate([
    'avatar' => File::image() // Creates ImageFile rule via File::image() factory
        ->between('100KB', '2MB') // Size constraints using toKilobytes() conversion
        ->extensions(['jpg', 'jpeg', 'png', 'webp']) // Whitelist extensions
        ->dimensions(
            Rule::dimensions()
                ->minWidth(300)
                ->maxWidth(3000)
                ->minHeight(300)
                ->maxHeight(2000)
        ),
]);

```

### Preventing MIME Type Spoofing and Extension Attacks

The `File::extensions()` method in [`src/Illuminate/Validation/Rules/File.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Validation/Rules/File.php) works in tandem with `buildMimetypes()` to create a whitelist of acceptable MIME types. This dual check mitigates attacks where a malicious user renames [`shell.php`](https://github.com/laravel/framework/blob/main/shell.php) to `shell.jpg` to bypass validation.

According to the source code, the `ImageFile` constructor explicitly sets the `mimetypes` validation rule to `image/jpeg,image/png,image/gif,image/bmp,image/svg+xml,image/webp` (with SVG optional via the `allowSvg` parameter). This strict typing prevents executable content from passing as an image.

### Blocking Image Bombs with Dimension Constraints

Image bombs (decompression bombs or extremely large dimension images) can exhaust server memory during processing. The `dimensions()` method in [`src/Illuminate/Validation/Rules/ImageFile.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Validation/Rules/ImageFile.php) accepts a `Dimensions` rule object from [`src/Illuminate/Validation/Rules/Dimensions.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Validation/Rules/Dimensions.php).

By setting `minWidth`, `maxWidth`, `minHeight`, and `maxHeight` constraints, you ensure that the image dimensions are validated before Laravel attempts to process the file, preventing memory exhaustion attacks that rely on malformed or maliciously crafted image headers.

## Secure Storage with UploadedFile and Filesystem Disks

Once validation passes, use the `UploadedFile` class in [`src/Illuminate/Http/UploadedFile.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Http/UploadedFile.php) to handle the physical storage. Never use the original client filename directly.

### Using Hash Names to Prevent Path Traversal

The `store()` method automatically generates a secure filename using `hashName()`, which creates a random SHA-256 hash with the original extension:

```php
$path = $request->file('photo')->store('photos', [
    'disk' => 'public',
]);

```

According to the source code in [`src/Illuminate/Http/UploadedFile.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Http/UploadedFile.php), the `store()` method calls `storeAs()` with `null` as the filename, triggering `hashName()` generation. This prevents path traversal attacks where a filename like `../../../etc/passwd` could overwrite system files, and it eliminates collisions between users uploading files with the same name.

### Configuring Public vs Private Disks

Configure your storage locations in [`config/filesystems.php`](https://github.com/laravel/framework/blob/main/config/filesystems.php). For web-accessible images, use the `public` disk:

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

```

Run `php artisan storage:link` to create a symbolic link from `public/storage` to `storage/app/public`. For sensitive images, use a `private` disk with `visibility` set to `private`, and serve them through a controller using `Storage::temporaryUrl()` for S3 or a streaming response for local files.

## Complete Implementation Example

Here is a production-ready controller method combining validation and secure storage:

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\File;

class ImageController extends Controller
{
    public function upload(Request $request)
    {
        // Validate: whitelist extensions, MIME types, size, and dimensions
        $validated = $request->validate([
            'avatar' => File::image()
                ->between('100KB', '2MB')
                ->extensions(['jpg', 'jpeg', 'png', 'webp'])
                ->dimensions(
                    Rule::dimensions()
                        ->minWidth(300)
                        ->maxWidth(2000)
                        ->ratio(1/1) // Optional: enforce square images
                ),
        ]);

        // Store with hashed filename on public disk
        $path = $request->file('avatar')->store('avatars', [
            'disk' => 'public',
        ]);

        // Return public URL
        return response()->json([
            'path' => $path,
            'url' => Storage::url($path),
        ]);
    }
}

```

## Summary

- **Validate first**: Use `File::image()` in [`src/Illuminate/Validation/Rules/File.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Validation/Rules/File.php) to enforce MIME types, extensions, size limits, and dimensions before any file touches the disk.
- **Hash filenames**: Use `UploadedFile::store()` in [`src/Illuminate/Http/UploadedFile.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Http/UploadedFile.php) to generate random hash-based filenames, preventing path traversal and collision attacks.
- **Disk isolation**: Configure separate `public` and `private` disks in [`config/filesystems.php`](https://github.com/laravel/framework/blob/main/config/filesystems.php) to control visibility and access patterns.
- **Dimension constraints**: Leverage `Rule::dimensions()` to block image bombs that could exhaust server memory.
- **Never trust client metadata**: Rely on Laravel's built-in validation rules rather than `clientExtension()` or `getClientOriginalName()` for security decisions.

## Frequently Asked Questions

### How do I prevent users from uploading malicious files disguised as images?

Combine extension whitelisting with MIME type validation using `File::image()->extensions(['jpg', 'png'])`. According to the source code in [`src/Illuminate/Validation/Rules/File.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Validation/Rules/File.php), the `buildMimetypes()` method creates a strict mapping of allowed extensions to MIME types, preventing attackers from bypassing validation by renaming executable files with image extensions.

### What is the best way to store uploaded images in Laravel?

Use the `store()` method on the `UploadedFile` instance, which generates a secure hash-based filename via `hashName()` as implemented in [`src/Illuminate/Http/UploadedFile.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Http/UploadedFile.php). Store the file on a dedicated disk configured in [`config/filesystems.php`](https://github.com/laravel/framework/blob/main/config/filesystems.php), using the `public` disk for web-accessible assets and `private` disks for sensitive content that requires authorization checks.

### How do I validate image dimensions before processing?

Chain the `dimensions()` method to your `File::image()` rule, passing a `Rule::dimensions()` object with `minWidth`, `maxWidth`, `minHeight`, and `maxHeight` constraints. This validation occurs in [`src/Illuminate/Validation/Rules/ImageFile.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Validation/Rules/ImageFile.php) before the file is processed, protecting against image bombs that could cause memory exhaustion when resizing or manipulating the image later.

### Should I use the original filename or a generated name for uploaded images?

Always use generated names via `store()` or `storeAs()` with `hashName()`. The original client filename available through `getClientOriginalName()` may contain path traversal sequences like `../` or malicious characters. The `hashName()` method in [`src/Illuminate/Http/UploadedFile.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Http/UploadedFile.php) generates a SHA-256 hash with the original extension, ensuring uniqueness while eliminating security risks associated with user-provided filenames.