Laravel Framework

File Storage & Uploads in Laravel

18 min Lesson 13 of 45

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.

  1. Create upload form with file input (accept only images)
  2. Validate: required, image type, max 2MB, min dimensions 100x100
  3. Store on public disk in "avatars" folder
  4. Generate unique filename using user ID and timestamp
  5. Delete old avatar if exists before uploading new one
  6. Update user model with avatar path
  7. 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.

  1. Create form with multiple file input (accept PDFs only)
  2. Validate each file: PDF type, max 5MB per file
  3. Store all files in "documents" folder with original names
  4. Save file metadata to documents table (name, path, size, mime_type)
  5. Create download route that streams file with correct headers
  6. List all uploaded documents with download links

Exercise 3: Image Gallery with Thumbnails

Build an image gallery that automatically creates thumbnails for uploaded images.

  1. Install Intervention Image package
  2. Create upload form accepting multiple images
  3. For each image, create two versions:
    • Original (max 1920x1080, 90% quality)
    • Thumbnail (200x200 cropped, 80% quality)
  4. Store both in public disk (photos/ and photos/thumbs/)
  5. Save paths to database with original filename
  6. Display gallery grid showing thumbnails
  7. 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.