Introduction to File Storage
Laravel provides a powerful filesystem abstraction that makes it easy to work with local filesystems, Amazon S3, and other cloud storage services. The unified API means you can switch storage providers without changing your application code.
Note: Laravel uses Flysystem, a powerful PHP package, to provide a consistent interface for various storage systems. This abstraction allows seamless switching between local and cloud storage.
Filesystem Configuration
Storage configuration is located in config/filesystems.php:
<?php
return [
'default' => env('FILESYSTEM_DISK', 'local'),
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
'throw' => false,
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
'throw' => false,
],
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
],
],
'links' => [
public_path('storage') => storage_path('app/public'),
],
];
Understanding Disks
Laravel storage "disks" represent different storage locations:
- local: Stores files in
storage/app (private, not web-accessible)
- public: Stores files in
storage/app/public (publicly accessible via symbolic link)
- s3: Stores files on Amazon S3 or compatible services
Tip: Use the local disk for private files (invoices, documents) and public disk for public files (user avatars, product images).
The Public Disk & Symbolic Link
To make files in storage/app/public accessible from the web, create a symbolic link:
# Create symbolic link
php artisan storage:link
# This creates: public/storage -> storage/app/public
# Now files stored in storage/app/public can be accessed via:
# https://yourapp.com/storage/filename.jpg
Storing Files
Use the Storage facade to store files on any configured disk:
<?php
use Illuminate\Support\Facades\Storage;
// Store file on default disk
Storage::put('file.txt', 'Contents');
// Store on specific disk
Storage::disk('public')->put('avatars/user-1.jpg', $fileContents);
// Store uploaded file
if ($request->hasFile('photo')) {
$path = $request->file('photo')->store('photos');
// Returns: photos/randomname.jpg
}
// Store with specific name
$path = $request->file('photo')->storeAs(
'photos',
'user-avatar.jpg'
);
// Store on specific disk
$path = $request->file('photo')->store('photos', 's3');
// Store with visibility
Storage::disk('s3')->put(
'file.jpg',
$contents,
'public' // visibility: public or private
);
Retrieving Files
Retrieve and work with stored files:
<?php
use Illuminate\Support\Facades\Storage;
// Check if file exists
if (Storage::disk('local')->exists('file.txt')) {
// File exists
}
// Get file contents
$contents = Storage::get('file.txt');
// Download file (returns response)
return Storage::download('file.pdf');
// Download with custom name
return Storage::download('file.pdf', 'invoice.pdf');
// Get file size (bytes)
$size = Storage::size('file.txt');
// Get last modified time (Unix timestamp)
$time = Storage::lastModified('file.txt');
// Get file MIME type
$mime = Storage::mimeType('file.jpg');
Generating URLs
Generate public URLs for stored files:
<?php
// Generate URL for public disk
$url = Storage::disk('public')->url('avatars/user-1.jpg');
// Returns: http://yourapp.com/storage/avatars/user-1.jpg
// Generate temporary URL (S3 only, expires after 5 minutes)
$url = Storage::disk('s3')->temporaryUrl(
'file.jpg',
now()->addMinutes(5)
);
// Temporary URL with custom headers
$url = Storage::disk('s3')->temporaryUrl(
'file.jpg',
now()->addMinutes(5),
['ResponseContentType' => 'application/pdf']
);
// In Blade views
<img src="{{ Storage::url('avatars/user.jpg') }}" alt="Avatar">
// Using asset helper (for public disk)
<img src="{{ asset('storage/avatars/user.jpg') }}" alt="Avatar">
Deleting Files
Remove files from storage:
<?php
use Illuminate\Support\Facades\Storage;
// Delete single file
Storage::delete('file.txt');
// Delete multiple files
Storage::delete(['file1.txt', 'file2.txt']);
// Delete from specific disk
Storage::disk('s3')->delete('folder/file.txt');
// Delete directory
Storage::deleteDirectory('uploads/temp');
File Upload Validation
Always validate uploaded files for security and integrity:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class FileUploadController extends Controller
{
public function upload(Request $request)
{
// Validate uploaded file
$validated = $request->validate([
'photo' => [
'required',
'file',
'image', // Must be image (jpg, jpeg, png, bmp, gif, svg, webp)
'mimes:jpeg,png,jpg,gif', // Specific formats only
'max:2048', // Max size in kilobytes (2MB)
'dimensions:min_width=100,min_height=100,max_width=2000,max_height=2000',
],
'document' => [
'required',
'file',
'mimetypes:application/pdf,application/msword',
'max:5120', // 5MB
],
'video' => [
'required',
'file',
'mimetypes:video/mp4,video/mpeg',
'max:51200', // 50MB
],
]);
// File is valid, proceed with storage
$path = $request->file('photo')->store('uploads');
return response()->json([
'message' => 'File uploaded successfully',
'path' => $path,
]);
}
}
Security Warning: Never trust user-uploaded files. Always validate file types, sizes, and contents. Malicious files can compromise your server if not properly handled.
Practical Upload Example
Complete example with form, validation, and storage:
<!-- resources/views/upload.blade.php -->
<form action="{{ route('upload.store') }}" method="POST" enctype="multipart/form-data">
@csrf
<div>
<label for="avatar">Profile Photo</label>
<input type="file" name="avatar" id="avatar" accept="image/*">
@error('avatar')
<span class="error">{{ $message }}</span>
@enderror
</div>
<button type="submit">Upload</button>
</form>
<?php
// Controller
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class UploadController extends Controller
{
public function store(Request $request)
{
// Validate
$request->validate([
'avatar' => 'required|image|max:2048',
]);
// Get file
$file = $request->file('avatar');
// Generate unique filename
$filename = time() . '_' . $file->getClientOriginalName();
// Store file
$path = $file->storeAs(
'avatars',
$filename,
'public'
);
// Save path to database
auth()->user()->update([
'avatar' => $path,
]);
return redirect()->back()->with('success', 'Avatar updated!');
}
public function delete(Request $request)
{
$user = auth()->user();
// Delete old avatar
if ($user->avatar) {
Storage::disk('public')->delete($user->avatar);
}
$user->update(['avatar' => null]);
return redirect()->back()->with('success', 'Avatar deleted!');
}
}
Working with Directories
Manage directories and list files:
<?php
use Illuminate\Support\Facades\Storage;
// Get all files in directory
$files = Storage::files('uploads');
// Get all files recursively
$files = Storage::allFiles('uploads');
// Get directories
$directories = Storage::directories('uploads');
// Get directories recursively
$directories = Storage::allDirectories('uploads');
// Create directory
Storage::makeDirectory('uploads/2024');
// Copy file
Storage::copy('old.jpg', 'new.jpg');
// Move/rename file
Storage::move('old.jpg', 'new.jpg');
// Get file path (absolute path on local disk)
$path = Storage::path('file.txt');
Using Amazon S3
Configure and use Amazon S3 for file storage:
# Install AWS SDK
composer require league/flysystem-aws-s3-v3 "^3.0"
# Configure .env
AWS_ACCESS_KEY_ID=your-key-id
AWS_SECRET_ACCESS_KEY=your-secret-key
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=your-bucket-name
AWS_USE_PATH_STYLE_ENDPOINT=false
<?php
// Store on S3
Storage::disk('s3')->put('path/file.jpg', $contents);
// Store with public visibility
Storage::disk('s3')->put(
'path/file.jpg',
$contents,
'public'
);
// Get public URL
$url = Storage::disk('s3')->url('file.jpg');
// Generate temporary signed URL (expires in 5 minutes)
$url = Storage::disk('s3')->temporaryUrl(
'file.jpg',
now()->addMinutes(5)
);
// Set default disk to S3
// In .env: FILESYSTEM_DISK=s3
// Now Storage::put() automatically uses S3
Storage::put('file.jpg', $contents);
Custom Disks
Create custom storage disks for specific needs:
<?php
// config/filesystems.php
'disks' => [
'uploads' => [
'driver' => 'local',
'root' => storage_path('app/uploads'),
'url' => env('APP_URL').'/uploads',
'visibility' => 'public',
],
'backups' => [
'driver' => 'local',
'root' => storage_path('app/backups'),
],
'documents' => [
'driver' => 'local',
'root' => storage_path('app/documents'),
'throw' => false,
],
];
// Usage
Storage::disk('uploads')->put('file.pdf', $contents);
Storage::disk('backups')->put('backup.sql', $sqlDump);
Image Processing
Use Intervention Image for image manipulation:
# Install Intervention Image
composer require intervention/image
# Configure in config/app.php
'providers' => [
Intervention\Image\ImageServiceProvider::class,
],
'aliases' => [
'Image' => Intervention\Image\Facades\Image::class,
],
<?php
use Intervention\Image\Facades\Image;
public function upload(Request $request)
{
$request->validate([
'photo' => 'required|image|max:5120',
]);
$file = $request->file('photo');
// Resize image
$image = Image::make($file)
->resize(800, 600, function ($constraint) {
$constraint->aspectRatio(); // Maintain aspect ratio
$constraint->upsize(); // Prevent upsizing
})
->encode('jpg', 85); // Convert to JPG with 85% quality
// Generate filename
$filename = uniqid() . '.jpg';
// Store resized image
Storage::disk('public')->put(
'photos/' . $filename,
(string) $image
);
// Create thumbnail
$thumbnail = Image::make($file)
->fit(200, 200) // Crop to exact size
->encode('jpg', 80);
Storage::disk('public')->put(
'photos/thumbs/' . $filename,
(string) $thumbnail
);
return back()->with('success', 'Photo uploaded!');
}
Exercise 1: Avatar Upload System
Create a complete avatar upload system with validation, storage, and display.
- Create upload form with file input (accept only images)
- Validate: required, image type, max 2MB, min dimensions 100x100
- Store on public disk in "avatars" folder
- Generate unique filename using user ID and timestamp
- Delete old avatar if exists before uploading new one
- Update user model with avatar path
- Display avatar in user profile using Storage::url()
Exercise 2: Multi-File Upload
Create a document upload system that accepts multiple PDF files at once.
- Create form with multiple file input (accept PDFs only)
- Validate each file: PDF type, max 5MB per file
- Store all files in "documents" folder with original names
- Save file metadata to documents table (name, path, size, mime_type)
- Create download route that streams file with correct headers
- List all uploaded documents with download links
Exercise 3: Image Gallery with Thumbnails
Build an image gallery that automatically creates thumbnails for uploaded images.
- Install Intervention Image package
- Create upload form accepting multiple images
- For each image, create two versions:
- Original (max 1920x1080, 90% quality)
- Thumbnail (200x200 cropped, 80% quality)
- Store both in public disk (photos/ and photos/thumbs/)
- Save paths to database with original filename
- Display gallery grid showing thumbnails
- Clicking thumbnail opens full-size image in modal
Summary
In this lesson, you learned:
- Laravel filesystem configuration and disk types
- The difference between local, public, and S3 disks
- Creating symbolic links for public storage
- Storing and retrieving files with Storage facade
- File upload validation rules for security
- Generating public and temporary URLs
- Working with directories and file operations
- Configuring and using Amazon S3
- Creating custom storage disks
- Processing images with Intervention Image
Next Lesson: We'll explore Laravel Collections, a powerful tool for working with arrays and data manipulation.