Why Use AWS S3 for File Storage in Laravel 11?
Before we get into the technical steps, let's explore the benefits of using AWS S3 bucket in Laravel 11:
- Scalability: Handle unlimited file storage without worrying about server limits.
- Security: Features like encryption, access controls, and versioning protect your data.
- Cost-Effective: Pay only for what you use, with global CDN integration for faster delivery.
- Laravel Integration: Laravel's built-in Filesystem abstraction makes S3 seamless—no custom code needed.
- Performance: Offload file serving from your app server to S3, reducing load times.
Compared to local storage, S3 is ideal for production apps. According to AWS stats, S3 offers 99.999999999% durability—perfect for mission-critical file management in Laravel 11 projects.

Table Of Content
1 Prerequisites for Integrating AWS S3 with Laravel 11
To follow this guide on how to use AWS S3 bucket in Laravel 11, ensure you have:
- An active AWS account with billing enabled.
- Laravel 11 installed (via Composer:
composer create-project laravel/laravel my-app). - PHP 8.2+ and Composer on your development machine.
- Basic knowledge of Laravel's config files and Artisan commands.
2 Introduction
3 Install Laravel Project
Use the following command to install new Laravel Project.
composer create-project laravel/laravel laravel-amazon-s3-app
Then, navigate to your project directory:
cd laravel-amazon-s3-app
4 Set Up an AWS S3 Bucket
The first step to store files in AWS S3 with Laravel 11 is creating a bucket. Follow these sub-steps:
Create an S3 Bucket in AWS Console
- Log in to the AWS Management Console.
- Navigate to S3 and click "Create bucket."
- Enter a unique bucket name (e.g., "my-laravel11-app-files"). Choose a region close to your users (e.g., US West for LA-based apps).
- Enable versioning and server-side encryption for security.
- Under permissions, create a bucket policy for public reads if needed (e.g., for images).
Tip: Bucket names must be globally unique. For private files, skip public access.
Generate AWS IAM Credentials
To securely connect Laravel 11 to S3, create an IAM user with S3 permissions:
- Go to IAM in AWS Console > Users > Add user.
- Select "Programmatic access" and attach the "AmazonS3FullAccess" policy (or a custom one for least privilege).
- Save the Access Key ID and Secret Access Key—they'll go into Laravel's config.
Security Best Practice: Never hardcode keys in code. Use environment variables instead.
5 Install and Configure the AWS SDK in Laravel 11
Laravel 11 uses the Flysystem adapter for S3 integration. No extra packages needed, but ensure your composer.json includes the AWS SDK.
Run this in your Laravel project root:
composer require league/flysystem-aws-s3-v3 "^3.0"
This pulls in the AWS SDK for PHP. Verify installation with composer show.
6 Configure AWS Credentials in .env file
Now, update your .env file with the credentials:
AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key
AWS_DEFAULT_REGION=us-west-2
AWS_BUCKET=my-laravel11-app-files
AWS_ENDPOINT=''
AWS_URL=''
7 Configure Filesystem in Laravel
Open config/filesystems.php and add an S3 disk:
'disks' => [
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-west-2'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
'throw' => false,
],
], 8 Create New Controller - FileController
Route::post('/upload', [FileController::class, 'upload']);
This stores the file in your S3 bucket under the "uploads" folder. For public files, set visibility: 'public' in store().
To fetch a file URL:
$url = Storage::disk('s3')->url('uploads/filename.jpg');
echo $url; // Outputs: https://my-bucket.s3.amazonaws.com/uploads/filename.jpg
In a view, use <img src="{{ Storage::disk('s3')->url($path) }}" alt="Uploaded image">. This ensures fast CDN delivery.
Delete Files from S3
Simple deletion:
if (Storage::disk('s3')->exists($path)) {
Storage::disk('s3')->delete($path);
return 'File deleted from S3 successfully!';
}
For bulk deletes, use deleteDirectory().
php artisan make:controller FileController
app/Http/Controllers/FileController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Str;
class FileController extends Controller
{
/**
* Display a listing of files in S3.
*/
public function index()
{
$files = Storage::disk('s3')->files('uploads'); // List files in 'uploads' directory
$fileData = [];
foreach ($files as $file) {
$fileData[] = [
'path' => $file,
'url' => Storage::disk('s3')->url($file),
'size' => Storage::disk('s3')->size($file),
'lastModified' => Storage::disk('s3')->lastModified($file),
];
}
return view('files', compact('fileData'));
}
/**
* Upload a file to S3.
*/
public function upload(Request $request)
{
$request->validate([
'file' => 'required|file|mimes:jpg,png,pdf|max:2048', // Validate file type and size (2MB)
]);
try {
// Store file with unique name to avoid overwrites
$originalName = $request->file('file')->getClientOriginalName();
$path = 'uploads/' . Str::uuid() . '-' . $originalName;
Storage::disk('s3')->put(
$path,
file_get_contents($request->file('file')->getRealPath()),
'public' // Set visibility to public for accessible URLs
);
return Redirect::route('files.index')->with('success', 'File uploaded successfully: ' . $path);
} catch (\Exception $e) {
return Redirect::route('files.index')->with('error', 'Upload failed: ' . $e->getMessage());
}
}
/**
* Delete a file from S3.
*/
public function delete($path)
{
// Decode if needed (assuming path is passed as-is or base64 for URL safety)
// For simplicity, assuming direct path; in production, sanitize!
if (Storage::disk('s3')->exists($path)) {
Storage::disk('s3')->delete($path);
return Redirect::route('files.index')->with('success', 'File deleted successfully.');
}
return Redirect::route('files.index')->with('error', 'File not found.');
}
}
?>
9 Create Index Blade View File
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Laravel 11 S3 File Manager</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
.success { color: green; }
.error { color: red; }
</style>
</head>
<body>
<h1>AWS S3 File Manager in Laravel 11</h1>
@if (session('success'))
<p class="success">{{ session('success') }}</p>
@endif
@if (session('error'))
<p class="error">{{ session('error') }}</p>
@endif
<!-- Upload Form -->
<form action="{{ route('files.upload') }}" method="POST" enctype="multipart/form-data">
@csrf
<input type="file" name="file" required>
<button type="submit">Upload to S3</button>
</form>
<!-- File List Table -->
<h2>Uploaded Files</h2>
@if (empty($fileData))
<p>No files uploaded yet.</p>
@else
<table>
<thead>
<tr>
<th>Path</th>
<th>URL</th>
<th>Size (bytes)</th>
<th>Last Modified</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach ($fileData as $file)
<tr>
<td>{{ $file['path'] }}</td>
<td><a href="{{ $file['url'] }}" target="_blank">View</a></td>
<td>{{ $file['size'] }}</td>
<td>{{ date('Y-m-d H:i:s', $file['lastModified']) }}</td>
<td>
<form action="{{ route('files.delete', $file['path']) }}" method="POST" style="display:inline;">
@csrf
@method('DELETE')
<button type="submit" onclick="return confirm('Are you sure?');">Delete</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
@endif
</body>
</html>
10 Define a Route
routes/web.php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\FileController;
Route::get('/files', [FileController::class, 'index'])->name('files.index');
Route::post('/files/upload', [FileController::class, 'upload'])->name('files.upload');
Route::delete('/files/{path}', [FileController::class, 'delete'])->name('files.delete');
11 Folder Structure
12 Run Laravel Server to Test the App
php artisan serve
Visit the URL http://127.0.0.1:8000 13 Conclusion: Streamline Your Laravel 11 App with AWS S3
Integrating how to use AWS S3 bucket in Laravel 11 unlocks powerful file storage capabilities, making your app more robust and scalable. Follow these steps, and you'll be managing files like a pro. If you encounter issues, check the official Laravel docs or AWS support.
Ready to implement? Start your Laravel 11 project today! Share your experience in the comments below or check our other guides on Laravel 11 best practices.
To optimize your setup:
- Error Handling: Wrap operations in try-catch for AWS exceptions (e.g.,
Aws\S3\Exception\S3Exception). - Presigned URLs: For temporary access, generate URLs with
Storage::disk('s3')->temporaryUrl($path, now()->addMinutes(5)). - Migration to Production: Use Laravel's queue system for large uploads to avoid timeouts.
- Cost Optimization: Enable S3 lifecycle policies to archive old files automatically.
Common Issue: "Credentials not found"? Double-check your .env vars and clear config cache with php artisan config:clear.
Written by Revathi M
PHP Developer & Technical Writer · 10+ years building web applications with CodeIgniter and Laravel
Revathi specializes in PHP backend development, authentication systems, and REST API design. She writes practical, production-tested tutorials at Get Sample Code to help developers build secure applications faster.
Frequently Asked Questions
You need PHP 8.2 or higher, Composer, a fresh Laravel 11 project, and an AWS account.
Run `composer create-project laravel/laravel project-name`, then `cd project-name`.
Log into AWS Console, go to S3 > Create bucket (choose a unique name and region). Then go to IAM > Users > Create user, attach `AmazonS3FullAccess` policy, and generate Access Key ID and Secret Access Key.
Run `composer require league/flysystem-aws-s3-v3`.
Add these lines: `AWS_ACCESS_KEY_ID=your-key`, `AWS_SECRET_ACCESS_KEY=your-secret`, `AWS_DEFAULT_REGION=your-region`, `AWS_BUCKET=your-bucket-name`, and optionally `AWS_URL`, `AWS_ENDPOINT`, `AWS_USE_PATH_STYLE_ENDPOINT=false`.
In the `disks` array, add or update the 's3' configuration with driver 's3', key, secret, region, bucket, url, endpoint, and use_path_style_endpoint from env().
Run `php artisan make:controller UploadController`.
In the upload method, use `$request->file('file')->store('uploads', 's3')` to save to the 'uploads' folder on S3, set visibility to 'private' if needed, and generate URL with `Storage::disk('s3')->url($path)`.
Use `Storage::disk('s3')->files()` (or `files('uploads')` for a specific folder) to get an array of file paths, then display them in a view.
Files are set to private by default in the tutorial. For public access, set visibility to 'public' with `Storage::disk('s3')->setVisibility($path, 'public')`, or update bucket policy to allow public reads.
Common on local/Windows setups. Download cacert.pem and set in php.ini, or temporarily add 'scheme' => 'http' in S3 config (not recommended for production).
Verify AWS_ACCESS_KEY_ID, SECRET, REGION, and BUCKET in `.env`. Ensure the IAM user has `AmazonS3FullAccess` or specific permissions like s3:PutObject, s3:GetObject.
Set visibility to 'public' during upload, or use `Storage::disk('s3')->temporaryUrl($path, now()->addMinutes(5))` for time-limited access.
Run `php artisan serve`, visit `/index` to upload files, and `/s3-files` to list them.
