Laravel file upload with validation example

By Priyash Patil | 5 min read | Updated: Jan 25, 2024 5PM UTC

Prerequisites and Initial Setup

Creating the File Upload Form

Step 1: Create the form view at resources/views/form-view.blade.php and paste the following content:

<!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Laravel File Upload Example</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
</head>

<body>
    <div class="container py-4" style="max-width: 768px;">
        <h1>Laravel file upload example</h1>
        <form method="POST" enctype="multipart/form-data" class="mb-3">
            @csrf

            <div class="mb-3">
                <label for="fileInput" class="form-label">Upload a Photo</label>
                <input type="file" class="form-control @error('photo') is-invalid @enderror" id="fileInput"
                    name="photo">
                @error('photo')
                    <div class="invalid-feedback">
                        {{ $message }}
                    </div>
                @enderror
            </div>

            <button type="submit" class="btn btn-primary">Upload</button>
        </form>

        {{-- Display the uploaded files --}}
        @if (session('files'))
            <div>
                <h3>Uploaded Files</h3>
                <a href="{{ session('files') }}" target="_blank">{{ session('files') }}</a>
            </div>
        @endif
    </div>
</body>

</html>
  1. In the above added the HTML I’m using Bootstrap to keep things simple and show some validation states. The form contains enctype="multipart/form-data" and method="POST"  attributes which allow sending file uploads to the server.
  2. The Form also includes @csrf injecting the CSRF token that is required by Laravel’s VerifyCsrfToken for CSRF Protection.
  3. The input field contains @error a directive to show any validation errors and the attribute name="photo" allows you to refer to the file on the server for processing.
  4. Lastly, the @if directive to show the path of the uploaded file. Which will be added from the controller.
  5. Note: We’re not adding required attribute on input filed. Because we want to test the backend validation too. So, for the production environment, you can the required attribute to enable native frontend validation.

Tip: in the above input field you can add an attribute accept="image/*" or anything else that is required. For example, if you want to restrict the file uploads to only PDF and Doc files then you could use accept="application/pdf,application/docx".

Setting Up the Controller and Validating Files

Step 2:  Create a new controller FileUploadController

php artisan make:controller FileUploadController

Then update the controller as follows:

<?php

namespace AppHttpControllers;

use IlluminateHttpRequest;
use IlluminateSupportFacadesStorage;

class FileUploadController extends Controller
{
    public function showForm()
    {
        return view('form-view');
    }

    public function handleFormUpload(Request $request)
    {
    	// Validate the file.
        $request->validate([
            'photo' => 'required|file|max:2000|mimes:jpg,png,webp,gif'
        ]);

		// Store the file.
        $path = $request->file('photo')->store('/public_uploads', ['disk' => 'public']);

        return redirect()->route('file.upload')->with(['files' => Storage::disk('public')->url($path)]);
    }
}
  1. In the controller, we have two methods showFrom and second, is handleFormUpload. The showForm returns the view that we created in Step 1.
  2. The handleFormUpload does the validation and then processes the file storage and finally redirects to the route file.upload with some data flash that includes the file’s complete URL.

The Validation Rules

For the validation, we are using the validated method on request which takes the validation rules as the first parameter. You can find out more about the rules here: validation rules.

Step 3: Update the web.php with the following routes and do the storage:link:

Route::get('/form-example', [AppHttpControllersFileUploadController::class, 'showForm'])->name('file.upload');
Route::post('/form-example', [AppHttpControllersFileUploadController::class, 'handleFormUpload']);

Finally, run the storage link to link to public storage disk to the public folder.

php artisan storage:link

If everything is done correctly you should now be able to test the file uploads:

File upload successful screenshot

In the controller, we are doing: $path = $request->file('photo')->store('/public_uploads', ['disk' => 'public']);, which means we are using the public disk to store the files. To make the public disk accessible we have to link storage/app/public to public/storage. More about this is below.

Types of Uploads

Laravel supports multiple file storage drivers which can be configured in config/filesystems.php. In the above example, we used a disk called public. Which comes configured by default. There are two disks in config/filesystems.php one local and the second is public. Both disks point to storage/app and storage/app/public respectively. Since these are completely separate folders from the projects public folder. To link the public (storage/app/public) disk to public folder you must run the php artisan storage:link. This creates a symbolic link between the two folders. This is required because the public folder is included in Git and any changes to this would result in Git related issues. Also, the file storage must be kept separate to avoid any deployment-related issues.

Tip: You can configure the default disk filesystem disk by updating the FILESYSTEM_DISK=local variable in your .env file. Laravel by default comes with local a default disk configured.

Private Uploads

Laravel is by default configured to use local disk as the default disk. For private file uploads, update the handleFormUpload method in FileUploadController to store files in a private disk:

$path = $request->file('photo')->store('private_uploads', ['disk' => 'local']);

Note: To access these files, create a route that uses a controller method to serve the file after validating user permissions.

Public Uploads

For public uploads, the example given uses the public disk. Public files are accessible directly via a URL. The php artisan storage:link command creates a symbolic link between public/storage and storage/app/public, making files in the public disk accessible from the web.

Multiple File Uploads

To handle multiple file uploads, modify the form view to allow multiple files and update the handleFormUpload method:

Update the input field in the form view with name="photos[]" multiple attributes on input and the code that shows the uploaded files list:

    {{-- ... --}}
        <input type="file" class="form-control" id="fileInput" name="photos[]" multiple>

        {{-- ... --}}
        
        {{-- Display the uploaded files --}}
        @if (session('files'))
            <div>
                <h3>Uploaded Files</h3>
                @foreach (session('files') as $file)
                    <a href="{{ $file }}" target="_blank">{{ $file }}</a>
                @endforeach
            </div>
        @endif
        
    {{-- ... --}}

And then update the handleFormUpload method to loop through and store all files:

    // ...
    public function handleFormUpload(Request $request)
    {
        $request->validate([
            'photos' => 'required|array',
            'photos.*' => 'required|file|max:2000|mimes:jpg,png,webp,gif'
        ]);

        $storedFiles = [];

        foreach ($request->file('photos') as $file) {
            $path = $file->store('public_uploads', ['disk' => 'public']);
            $storedFiles[] = Storage::disk('public')->url($path);
        }

        return redirect()->route('file.upload')->with(['files' => $storedFiles]);
    }
    
    // ...

AWS S3 Bucket Uploads

Along with public and local disks, the filesystems.php contains a s3 disk. To use this you must have an AWS S3 bucket and its credentials configured in .env.

AWS_ACCESS_KEY_ID=your_access_key_id
AWS_SECRET_ACCESS_KEY=your_secret_access_key
AWS_DEFAULT_REGION=your_bucket_region
AWS_BUCKET=your_bucket_name

Once the AWS credentials are configured you can use the disk as you would do with local disk. You can use the visibility option to set the file’s visibility public or private.

$request->file('photo')->store('public_uploads', ['disk' => 's3', 'visibility' => 'public']);

Note: Depending on your S3 bucket configuration your files may be public or private. I suggest creating the bucket as a private default policy, which AWS automatically creates without any additional configuration.

Conclusion

In this guide, we have explored various aspects of file uploads in Laravel, from basic single file uploads to more complex scenarios like private, public, and multiple file uploads, as well as integrating with AWS S3.

These instructions provide a comprehensive understanding for developers to effectively implement file upload functionality in their Laravel projects, accommodating a range of requirements and ensuring a secure and efficient user experience.

Keep the Conversation Going

I hope you found this post helpful! If you have any questions or feedback, feel free to reach out. You can also find me on X (Twitter) @priyashpatil for additional insights and updates on my latest content.

Related

Featured on laravel-news.com and benjamincrozat.com.

Optimized image uploads with CKEditor and Laravel

By Priyash Patil on Friday, 03 November 2023

Bootstrap 5 Remove Unused CSS with Vite and PurgeCSS

By Priyash Patil on Wednesday, 17 January 2024

Laravel Vite Deploy Assets to Global CDN

By Priyash Patil on Friday, 19 January 2024