File Storage and Upload
Laravel provides a powerful filesystem abstraction that makes it easy to work with local filesystems, Amazon S3, and other cloud storage services. The Storage facade provid
Laravel provides a powerful filesystem abstraction that makes it easy to work with local filesystems, Amazon S3, and other cloud storage services. The Storage facade provides a simple, unified API for interacting with various storage systems.
Laravel supports multiple storage "disks" configured in config/filesystems.php:
storage/appstorage/app/public (publicly accessible)Configuration:
1<?php
2declare(strict_types=1);
3
4// config/filesystems.php
5return [
6 'default' => env('FILESYSTEM_DISK', 'local'),
7
8 'disks' => [
9 'local' => [
10 'driver' => 'local',
11 'root' => storage_path('app'),
12 'throw' => false,
13 ],
14
15 'public' => [
16 'driver' => 'local',
17 'root' => storage_path('app/public'),
18 'url' => env('APP_URL').'/storage',
19 'visibility' => 'public',
20 'throw' => false,
21 ],
22
23 's3' => [
24 'driver' => 's3',
25 'key' => env('AWS_ACCESS_KEY_ID'),
26 'secret' => env('AWS_SECRET_ACCESS_KEY'),
27 'region' => env('AWS_DEFAULT_REGION'),
28 'bucket' => env('AWS_BUCKET'),
29 'url' => env('AWS_URL'),
30 'endpoint' => env('AWS_ENDPOINT'),
31 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
32 'throw' => false,
33 ],
34 ],
35
36 'links' => [
37 public_path('storage') => storage_path('app/public'),
38 ],
39];1# Create symbolic link from public/storage to storage/app/public
2php artisan storage:link1<?php
2declare(strict_types=1);
3
4namespace App\Http\Controllers;
5
6use Illuminate\Http\Request;
7use Illuminate\Http\Response;
8
9class FileUploadController extends Controller
10{
11 /**
12 * Upload a single file with validation.
13 */
14 public function upload(Request $request): Response
15 {
16 $validated = $request->validate([
17 'file' => 'required|file|max:10240', // Max 10MB
18 ]);
19
20 $file = $request->file('file');
21
22 // Store the file
23 $path = $file->store('uploads');
24
25 return response([
26 'message' => 'File uploaded successfully',
27 'path' => $path,
28 ], 201);
29 }
30}1<?php
2declare(strict_types=1);
3
4namespace App\Http\Controllers;
5
6use Illuminate\Http\Request;
7use Illuminate\Http\Response;
8
9class ImageUploadController extends Controller
10{
11 /**
12 * Upload an image with comprehensive validation.
13 */
14 public function uploadImage(Request $request): Response
15 {
16 $validated = $request->validate([
17 'image' => [
18 'required',
19 'image', // Must be jpeg, png, bmp, gif, svg, or webp
20 'mimes:jpeg,png,jpg,gif,webp',
21 'max:5120', // Max 5MB
22 'dimensions:min_width=100,min_height=100,max_width=4000,max_height=4000',
23 ],
24 ]);
25
26 $image = $request->file('image');
27
28 // Store with automatic extension
29 $path = $image->store('images', 'public');
30
31 // Get file information
32 $info = [
33 'original_name' => $image->getClientOriginalName(),
34 'mime_type' => $image->getMimeType(),
35 'size' => $image->getSize(), // bytes
36 'extension' => $image->extension(),
37 'path' => $path,
38 'url' => asset('storage/' . $path),
39 ];
40
41 return response($info, 201);
42 }
43}1<?php
2declare(strict_types=1);
3
4namespace App\Http\Controllers;
5
6use Illuminate\Http\Request;
7use Illuminate\Http\Response;
8
9class MultiFileUploadController extends Controller
10{
11 /**
12 * Upload multiple files.
13 */
14 public function uploadMultiple(Request $request): Response
15 {
16 $validated = $request->validate([
17 'files' => 'required|array|min:1|max:10',
18 'files.*' => 'required|file|mimes:pdf,doc,docx,jpg,png|max:10240',
19 ]);
20
21 $uploadedFiles = [];
22
23 foreach ($request->file('files') as $file) {
24 $path = $file->store('documents', 'public');
25
26 $uploadedFiles[] = [
27 'original_name' => $file->getClientOriginalName(),
28 'path' => $path,
29 'size' => $file->getSize(),
30 'url' => asset('storage/' . $path),
31 ];
32 }
33
34 return response([
35 'message' => count($uploadedFiles) . ' files uploaded successfully',
36 'files' => $uploadedFiles,
37 ], 201);
38 }
39}1<?php
2declare(strict_types=1);
3
4use Illuminate\Support\Facades\Storage;
5
6// Store file from request
7$path = $request->file('avatar')->store('avatars');
8$path = $request->file('avatar')->store('avatars', 'public'); // Specify disk
9
10// Store with custom filename
11$path = $request->file('avatar')->storeAs(
12 'avatars',
13 $request->user()->id . '.jpg',
14 'public'
15);
16
17// Store file contents from string
18Storage::put('file.txt', 'Contents');
19Storage::disk('public')->put('file.txt', 'Contents');
20
21// Append to file
22Storage::append('file.log', 'New log entry');
23
24// Prepend to file
25Storage::prepend('file.log', 'Beginning of log');
26
27// Store with visibility
28Storage::put('file.jpg', $contents, 'public'); // Public visibility
29Storage::put('file.jpg', $contents, 'private'); // Private visibility1<?php
2declare(strict_types=1);
3
4use Illuminate\Http\UploadedFile;
5use Illuminate\Support\Facades\Storage;
6
7class FileStorageService
8{
9 /**
10 * Store uploaded file with organized directory structure.
11 */
12 public function storeUploadedFile(
13 UploadedFile $file,
14 string $directory = 'uploads',
15 string $disk = 'public'
16 ): array {
17 // Generate unique filename
18 $filename = uniqid() . '_' . time() . '.' . $file->extension();
19
20 // Organize by year/month
21 $path = $directory . '/' . date('Y') . '/' . date('m');
22
23 // Store file
24 $fullPath = $file->storeAs($path, $filename, $disk);
25
26 return [
27 'path' => $fullPath,
28 'filename' => $filename,
29 'original_name' => $file->getClientOriginalName(),
30 'mime_type' => $file->getMimeType(),
31 'size' => $file->getSize(),
32 'url' => Storage::disk($disk)->url($fullPath),
33 ];
34 }
35
36 /**
37 * Store base64 encoded image.
38 */
39 public function storeBase64Image(
40 string $base64Data,
41 string $directory = 'images',
42 string $disk = 'public'
43 ): string {
44 // Decode base64
45 $imageData = base64_decode(preg_replace('#^data:image/\w+;base64,#i', '', $base64Data));
46
47 // Generate filename
48 $filename = uniqid() . '_' . time() . '.png';
49 $path = $directory . '/' . $filename;
50
51 // Store file
52 Storage::disk($disk)->put($path, $imageData);
53
54 return $path;
55 }
56}1<?php
2declare(strict_types=1);
3
4use Illuminate\Support\Facades\Storage;
5
6// Check if file exists
7if (Storage::exists('file.jpg')) {
8 // File exists
9}
10
11if (Storage::disk('s3')->exists('file.jpg')) {
12 // File exists on S3
13}
14
15// Get file contents
16$contents = Storage::get('file.txt');
17
18// Get file as download response
19return Storage::download('file.pdf');
20return Storage::download('file.pdf', 'custom-name.pdf');
21
22// Get file URL
23$url = Storage::url('file.jpg');
24$url = Storage::disk('public')->url('file.jpg');
25
26// Get temporary URL (S3)
27$url = Storage::temporaryUrl(
28 'file.jpg',
29 now()->addMinutes(5)
30);
31
32// Get file size
33$size = Storage::size('file.jpg');
34
35// Get last modified time
36$time = Storage::lastModified('file.jpg');
37
38// Get file metadata
39$metadata = Storage::getMetadata('file.jpg');1<?php
2declare(strict_types=1);
3
4namespace App\Http\Controllers;
5
6use Illuminate\Http\Response;
7use Illuminate\Support\Facades\Storage;
8use Symfony\Component\HttpFoundation\StreamedResponse;
9
10class FileDownloadController extends Controller
11{
12 /**
13 * Download file.
14 */
15 public function download(string $path): Response
16 {
17 if (!Storage::exists($path)) {
18 abort(404, 'File not found');
19 }
20
21 return Storage::download($path);
22 }
23
24 /**
25 * Stream large file.
26 */
27 public function stream(string $path): StreamedResponse
28 {
29 if (!Storage::exists($path)) {
30 abort(404, 'File not found');
31 }
32
33 return Storage::response($path);
34 }
35
36 /**
37 * Display image inline.
38 */
39 public function displayImage(string $path): Response
40 {
41 if (!Storage::exists($path)) {
42 abort(404, 'Image not found');
43 }
44
45 $file = Storage::get($path);
46 $type = Storage::mimeType($path);
47
48 return response($file, 200)->header('Content-Type', $type);
49 }
50}1<?php
2declare(strict_types=1);
3
4use Illuminate\Support\Facades\Storage;
5
6// Copy file
7Storage::copy('old/file.jpg', 'new/file.jpg');
8
9// Copy between disks
10Storage::disk('local')->copy('file.jpg', Storage::disk('s3')->path('file.jpg'));
11
12// Move file
13Storage::move('old/file.jpg', 'new/file.jpg');
14
15// Rename file (alias for move)
16Storage::move('old-name.jpg', 'new-name.jpg');1<?php
2declare(strict_types=1);
3
4use Illuminate\Support\Facades\Storage;
5
6// Delete single file
7Storage::delete('file.jpg');
8
9// Delete multiple files
10Storage::delete(['file1.jpg', 'file2.jpg', 'file3.jpg']);
11
12// Delete directory
13Storage::deleteDirectory('old-directory');
14
15// Delete all files in directory (keep directory)
16$files = Storage::files('directory');
17Storage::delete($files);1<?php
2declare(strict_types=1);
3
4use Illuminate\Support\Facades\Storage;
5
6// Get all files in directory
7$files = Storage::files('directory');
8
9// Get all files recursively
10$files = Storage::allFiles('directory');
11
12// Get all directories
13$directories = Storage::directories('directory');
14
15// Get all directories recursively
16$directories = Storage::allDirectories('directory');
17
18// Create directory
19Storage::makeDirectory('directory');
20
21// Check if directory exists
22if (Storage::directoryExists('directory')) {
23 // Directory exists
24}
25
26// Delete directory
27Storage::deleteDirectory('directory');1# Install Intervention Image
2composer require intervention/image1<?php
2declare(strict_types=1);
3
4namespace App\Services;
5
6use Illuminate\Http\UploadedFile;
7use Illuminate\Support\Facades\Storage;
8use Intervention\Image\ImageManager;
9use Intervention\Image\Drivers\Gd\Driver;
10
11class ImageService
12{
13 private ImageManager $imageManager;
14
15 public function __construct()
16 {
17 $this->imageManager = new ImageManager(new Driver());
18 }
19
20 /**
21 * Upload and process image with thumbnail.
22 */
23 public function uploadWithThumbnail(
24 UploadedFile $file,
25 int $maxWidth = 1920,
26 int $thumbnailWidth = 300
27 ): array {
28 // Load image
29 $image = $this->imageManager->read($file->getPathname());
30
31 // Resize main image
32 if ($image->width() > $maxWidth) {
33 $image->scale(width: $maxWidth);
34 }
35
36 // Generate filenames
37 $filename = uniqid() . '_' . time() . '.jpg';
38 $thumbnailFilename = 'thumb_' . $filename;
39
40 $path = 'images/' . date('Y/m');
41
42 // Save main image
43 $mainPath = $path . '/' . $filename;
44 Storage::put($mainPath, $image->toJpeg(quality: 90));
45
46 // Create and save thumbnail
47 $thumbnail = $this->imageManager->read($file->getPathname());
48 $thumbnail->scale(width: $thumbnailWidth);
49
50 $thumbnailPath = $path . '/thumbnails/' . $thumbnailFilename;
51 Storage::put($thumbnailPath, $thumbnail->toJpeg(quality: 80));
52
53 return [
54 'original' => [
55 'path' => $mainPath,
56 'url' => Storage::url($mainPath),
57 'width' => $image->width(),
58 'height' => $image->height(),
59 ],
60 'thumbnail' => [
61 'path' => $thumbnailPath,
62 'url' => Storage::url($thumbnailPath),
63 'width' => $thumbnail->width(),
64 'height' => $thumbnail->height(),
65 ],
66 ];
67 }
68
69 /**
70 * Create multiple image sizes.
71 */
72 public function createMultipleSizes(
73 UploadedFile $file,
74 array $sizes = ['small' => 300, 'medium' => 800, 'large' => 1920]
75 ): array {
76 $results = [];
77 $baseName = uniqid() . '_' . time();
78 $path = 'images/' . date('Y/m');
79
80 foreach ($sizes as $size => $width) {
81 $image = $this->imageManager->read($file->getPathname());
82 $image->scale(width: $width);
83
84 $filename = "{$baseName}_{$size}.jpg";
85 $fullPath = $path . '/' . $filename;
86
87 Storage::put($fullPath, $image->toJpeg(quality: 90));
88
89 $results[$size] = [
90 'path' => $fullPath,
91 'url' => Storage::url($fullPath),
92 'width' => $image->width(),
93 'height' => $image->height(),
94 ];
95 }
96
97 return $results;
98 }
99
100 /**
101 * Optimize image for web.
102 */
103 public function optimizeForWeb(UploadedFile $file): string
104 {
105 $image = $this->imageManager->read($file->getPathname());
106
107 // Resize if too large
108 if ($image->width() > 1920) {
109 $image->scale(width: 1920);
110 }
111
112 // Convert to WebP for better compression
113 $filename = uniqid() . '_' . time() . '.webp';
114 $path = 'images/' . date('Y/m') . '/' . $filename;
115
116 Storage::put($path, $image->toWebp(quality: 85));
117
118 return $path;
119 }
120}1# Install AWS SDK
2composer require --with-all-dependencies league/flysystem-aws-s3-v3 "^3.0"1# .env
2AWS_ACCESS_KEY_ID=your-key-id
3AWS_SECRET_ACCESS_KEY=your-secret-key
4AWS_DEFAULT_REGION=us-east-1
5AWS_BUCKET=your-bucket-name
6AWS_URL=https://your-bucket.s3.amazonaws.com
7FILESYSTEM_DISK=s31<?php
2declare(strict_types=1);
3
4use Illuminate\Support\Facades\Storage;
5
6// Upload to S3
7$path = $request->file('photo')->store('photos', 's3');
8
9// Upload with public visibility
10$path = Storage::disk('s3')->putFile('photos', $request->file('photo'), 'public');
11
12// Upload with custom name
13$path = Storage::disk('s3')->putFileAs(
14 'photos',
15 $request->file('photo'),
16 'custom-name.jpg',
17 'public'
18);
19
20// Get S3 URL
21$url = Storage::disk('s3')->url('photos/photo.jpg');
22
23// Get temporary URL (expires in 5 minutes)
24$url = Storage::disk('s3')->temporaryUrl(
25 'photos/photo.jpg',
26 now()->addMinutes(5)
27);
28
29// Set file visibility
30Storage::disk('s3')->setVisibility('photos/photo.jpg', 'public');
31$visibility = Storage::disk('s3')->getVisibility('photos/photo.jpg');
32
33// Delete from S3
34Storage::disk('s3')->delete('photos/photo.jpg');1<?php
2declare(strict_types=1);
3
4namespace App\Http\Controllers;
5
6use Illuminate\Http\Request;
7use Illuminate\Support\Facades\Storage;
8use Illuminate\Support\Facades\URL;
9
10class SecureFileController extends Controller
11{
12 /**
13 * Generate signed URL for file download.
14 */
15 public function generateDownloadLink(string $fileId): array
16 {
17 // Generate signed URL valid for 1 hour
18 $url = URL::temporarySignedRoute(
19 'files.download',
20 now()->addHour(),
21 ['file' => $fileId]
22 );
23
24 return [
25 'download_url' => $url,
26 'expires_at' => now()->addHour()->toDateTimeString(),
27 ];
28 }
29
30 /**
31 * Download file using signed URL.
32 */
33 public function download(Request $request, string $fileId)
34 {
35 // Validate signed URL
36 if (!$request->hasValidSignature()) {
37 abort(401, 'Invalid or expired download link');
38 }
39
40 // Get file path from database
41 $file = File::findOrFail($fileId);
42
43 if (!Storage::exists($file->path)) {
44 abort(404, 'File not found');
45 }
46
47 return Storage::download($file->path, $file->original_name);
48 }
49
50 /**
51 * Stream private file with authorization.
52 */
53 public function streamPrivateFile(Request $request, string $fileId)
54 {
55 $file = File::findOrFail($fileId);
56
57 // Check authorization
58 if (!$request->user()->can('view', $file)) {
59 abort(403, 'Unauthorized access');
60 }
61
62 if (!Storage::exists($file->path)) {
63 abort(404, 'File not found');
64 }
65
66 return Storage::response($file->path);
67 }
68}1<?php
2declare(strict_types=1);
3
4namespace App\Services;
5
6use App\Models\File;
7use Illuminate\Http\UploadedFile;
8use Illuminate\Support\Facades\Storage;
9use Illuminate\Support\Str;
10
11class FileUploadService
12{
13 /**
14 * Handle file upload with metadata storage.
15 */
16 public function upload(
17 UploadedFile $file,
18 int $userId,
19 string $directory = 'uploads',
20 string $disk = 'public'
21 ): File {
22 // Generate secure filename
23 $filename = $this->generateSecureFilename($file);
24
25 // Organize by date
26 $path = $directory . '/' . date('Y/m');
27
28 // Store file
29 $fullPath = $file->storeAs($path, $filename, $disk);
30
31 // Calculate file hash
32 $hash = hash_file('sha256', $file->getPathname());
33
34 // Save to database
35 return File::create([
36 'user_id' => $userId,
37 'disk' => $disk,
38 'path' => $fullPath,
39 'filename' => $filename,
40 'original_name' => $file->getClientOriginalName(),
41 'mime_type' => $file->getMimeType(),
42 'extension' => $file->extension(),
43 'size' => $file->getSize(),
44 'hash' => $hash,
45 ]);
46 }
47
48 /**
49 * Generate secure unique filename.
50 */
51 private function generateSecureFilename(UploadedFile $file): string
52 {
53 return Str::random(40) . '.' . $file->extension();
54 }
55
56 /**
57 * Delete file and database record.
58 */
59 public function delete(File $file): bool
60 {
61 Storage::disk($file->disk)->delete($file->path);
62 return $file->delete();
63 }
64}1<?php
2declare(strict_types=1);
3
4namespace App\Services;
5
6use Illuminate\Http\UploadedFile;
7
8class VirusScanService
9{
10 /**
11 * Scan uploaded file for viruses.
12 */
13 public function scan(UploadedFile $file): bool
14 {
15 // Using ClamAV
16 $output = [];
17 $returnCode = 0;
18
19 exec("clamscan {$file->getPathname()}", $output, $returnCode);
20
21 // Return code 0 means clean, 1 means infected
22 return $returnCode === 0;
23 }
24
25 /**
26 * Validate and scan file.
27 */
28 public function validateAndScan(UploadedFile $file): void
29 {
30 if (!$this->scan($file)) {
31 throw new \Exception('File failed virus scan');
32 }
33 }
34}1<?php
2declare(strict_types=1);
3
4namespace App\Http\Controllers;
5
6use Illuminate\Http\Request;
7use Illuminate\Support\Facades\Storage;
8
9class ChunkedUploadController extends Controller
10{
11 /**
12 * Handle chunked file upload.
13 */
14 public function uploadChunk(Request $request): array
15 {
16 $validated = $request->validate([
17 'file' => 'required|file',
18 'chunk_number' => 'required|integer|min:0',
19 'total_chunks' => 'required|integer|min:1',
20 'identifier' => 'required|string',
21 ]);
22
23 $identifier = $validated['identifier'];
24 $chunkNumber = $validated['chunk_number'];
25 $totalChunks = $validated['total_chunks'];
26
27 // Store chunk
28 $chunkPath = "chunks/{$identifier}/chunk_{$chunkNumber}";
29 $request->file('file')->storeAs('', $chunkPath);
30
31 // Check if all chunks uploaded
32 if ($this->allChunksUploaded($identifier, $totalChunks)) {
33 $finalPath = $this->mergeChunks($identifier, $totalChunks);
34
35 return [
36 'message' => 'Upload complete',
37 'path' => $finalPath,
38 'complete' => true,
39 ];
40 }
41
42 return [
43 'message' => 'Chunk uploaded',
44 'chunk' => $chunkNumber,
45 'complete' => false,
46 ];
47 }
48
49 /**
50 * Check if all chunks are uploaded.
51 */
52 private function allChunksUploaded(string $identifier, int $totalChunks): bool
53 {
54 for ($i = 0; $i < $totalChunks; $i++) {
55 if (!Storage::exists("chunks/{$identifier}/chunk_{$i}")) {
56 return false;
57 }
58 }
59 return true;
60 }
61
62 /**
63 * Merge all chunks into final file.
64 */
65 private function mergeChunks(string $identifier, int $totalChunks): string
66 {
67 $finalPath = "uploads/{$identifier}";
68 $finalFile = Storage::path($finalPath);
69
70 $output = fopen($finalFile, 'wb');
71
72 for ($i = 0; $i < $totalChunks; $i++) {
73 $chunkPath = "chunks/{$identifier}/chunk_{$i}";
74 $chunk = fopen(Storage::path($chunkPath), 'rb');
75
76 stream_copy_to_stream($chunk, $output);
77 fclose($chunk);
78 }
79
80 fclose($output);
81
82 // Clean up chunks
83 Storage::deleteDirectory("chunks/{$identifier}");
84
85 return $finalPath;
86 }
87}---
Laravel's file storage system provides:
Key Best Practices:
In the next lesson, we'll explore API Development with Resources in Laravel.
Your progress is saved in this browser, on this device only.