Prerequisites and Initial Setup
- A Laravel 6 or later version (up to Laravel 10) application.
- Basic knowledge of Laravel routing and Blade directives.
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>
- 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"andmethod="POST"attributes which allow sending file uploads to the server. - The Form also includes
@csrfinjecting the CSRF token that is required by Laravel’sVerifyCsrfTokenfor CSRF Protection. - The input field contains
@errora directive to show any validation errors and the attributename="photo"allows you to refer to the file on the server for processing. - Lastly, the
@ifdirective to show the path of the uploaded file. Which will be added from the controller. - Note: We’re not adding
requiredattribute 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)]);
}
}
- In the controller, we have two methods
showFromand second, ishandleFormUpload. TheshowFormreturns the view that we created in Step 1. - The
handleFormUploaddoes the validation and then processes the file storage and finally redirects to the routefile.uploadwith 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.
- File Size Validation: For file size validation we are specifying
max:2000which means the maximum file size should not exceed 2MB size. Based on (1MB = 1000) you can fine-tune the option. - File Type Validation: For tile type validation we are specifying
mimes:jpg,png,webp,gifwhich means we are only allowing file type of images. You can change these as per your requirements.
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:

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.