Documentation Index
Fetch the complete documentation index at: https://kawax.biz/llms.txt
Use this file to discover all available pages before exploring further.
What is file storage?
Laravel provides a powerful filesystem abstraction built on top of Flysystem. A single, consistent API works across local storage, SFTP, Amazon S3, and compatible services — switching drivers requires no changes to your application code.
Changing the driver doesn’t change your code. Use the local driver in development and S3 in production without touching your application logic.
Configuration
Filesystem configuration lives in config/filesystems.php. A disk is a named combination of a driver and a storage location.
| Driver | Use case |
|---|
local | Server’s local filesystem |
public | Publicly accessible local disk |
s3 | Amazon S3 or S3-compatible services |
sftp | SFTP servers |
ftp | FTP servers |
The local driver
The local driver operates relative to the root directory defined in your filesystems configuration. The default root is storage/app/private:
use Illuminate\Support\Facades\Storage;
Storage::disk('local')->put('example.txt', 'Contents');
// Writes to storage/app/private/example.txt
Change the default disk
Set the FILESYSTEM_DISK environment variable to switch the default disk:
The public disk and symbolic links
The public disk is for files that should be accessible from the web. By default it uses storage/app/public. To make those files accessible, create a symbolic link from public/storage to storage/app/public:
Generate a URL to the file
After the link is created, use the asset helper to build a URL:echo asset('storage/file.txt');
You can add extra symbolic links in the links array in config/filesystems.php:'links' => [
public_path('storage') => storage_path('app/public'),
public_path('images') => storage_path('app/images'),
],
Remove all symbolic links with:
php artisan storage:unlink
Basic operations
Reading files
use Illuminate\Support\Facades\Storage;
// Get file contents as a string
$contents = Storage::get('file.jpg');
// Read and decode a JSON file
$data = Storage::json('orders.json');
// Check existence
if (Storage::exists('file.jpg')) {
// File exists
}
if (Storage::missing('file.jpg')) {
// File does not exist
}
Writing files
// Write content to a file
Storage::put('file.jpg', $contents);
// Write a stream resource
Storage::put('file.jpg', $resource);
// Append to a file
Storage::prepend('file.log', 'Prepended text');
Storage::append('file.log', 'Appended text');
// Copy and move files
Storage::copy('old/file.jpg', 'new/file.jpg');
Storage::move('old/file.jpg', 'new/file.jpg');
When put fails it returns false by default. Set 'throw' => true on the disk configuration to throw an exception instead.
Deleting files
// Delete a single file
Storage::delete('file.jpg');
// Delete multiple files
Storage::delete(['file.jpg', 'file2.jpg']);
// Delete from a specific disk
Storage::disk('s3')->delete('path/file.jpg');
Download responses
// Prompt the browser to download the file
return Storage::download('file.jpg');
// With a custom file name and headers
return Storage::download('file.jpg', 'my-file.jpg', $headers);
Generating URLs
Regular URLs
use Illuminate\Support\Facades\Storage;
$url = Storage::url('file.jpg');
The local driver returns a relative URL such as /storage/file.jpg. The s3 driver returns the full remote URL.
Temporary URLs
Generate a time-limited URL for private files. Available for the local and s3 drivers:
use Illuminate\Support\Facades\Storage;
$url = Storage::temporaryUrl(
'file.jpg',
now()->addMinutes(5)
);
Pass additional S3 request parameters when needed:
$url = Storage::temporaryUrl(
'file.jpg',
now()->addMinutes(5),
[
'ResponseContentType' => 'application/octet-stream',
'ResponseContentDisposition' => 'attachment; filename=file.jpg',
]
);
For client-side direct uploads to S3, use temporaryUploadUrl:['url' => $url, 'headers' => $headers] = Storage::temporaryUploadUrl(
'file.jpg',
now()->addMinutes(5)
);
Uploading files
Automatic file name (store)
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UserAvatarController extends Controller
{
public function update(Request $request): string
{
// File name is generated automatically from the MIME type
$path = $request->file('avatar')->store('avatars');
return $path;
}
}
Explicit file name (storeAs)
$path = $request->file('avatar')->storeAs(
'avatars',
$request->user()->id
);
Upload to a specific disk
$path = $request->file('avatar')->store(
'avatars/' . $request->user()->id,
's3'
);
Using the Storage facade
use Illuminate\Support\Facades\Storage;
// Auto-generated file name
$path = Storage::putFile('avatars', $request->file('avatar'));
// Explicit file name
$path = Storage::putFileAs(
'avatars',
$request->file('avatar'),
$request->user()->id
);
getClientOriginalName() and getClientOriginalExtension() can be tampered with by the user. Use hashName() for the file name and extension() to determine the extension from the MIME type:$file = $request->file('avatar');
$name = $file->hashName(); // Unique random name
$extension = $file->extension(); // Derived from MIME type
File visibility
Flysystem controls public and private access through visibility:
use Illuminate\Support\Facades\Storage;
// Set visibility when writing
Storage::put('file.jpg', $contents, 'public');
// Get or change visibility
$visibility = Storage::getVisibility('file.jpg');
Storage::setVisibility('file.jpg', 'public');
Upload and immediately mark a file as public:
$path = $request->file('avatar')->storePublicly('avatars', 's3');
Working with multiple disks
// Write to the default disk
Storage::put('avatars/1', $content);
// Write to the s3 disk
Storage::disk('s3')->put('avatars/1', $content);
// Create a disk on the fly
$disk = Storage::build([
'driver' => 'local',
'root' => '/path/to/root',
]);
$disk->put('image.jpg', $content);
Amazon S3 configuration
Install the package
composer require league/flysystem-aws-s3-v3 "^3.0" --with-all-dependencies
Environment variables
AWS_ACCESS_KEY_ID=your-key-id
AWS_SECRET_ACCESS_KEY=your-secret-access-key
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=your-bucket-name
AWS_USE_PATH_STYLE_ENDPOINT=false
S3-compatible services such as DigitalOcean Spaces, Cloudflare R2, and Vultr Object Storage work with the s3 driver. Add an endpoint option pointing to the service’s URL:'endpoint' => env('AWS_ENDPOINT', 'https://your-endpoint.example.com'),
use Illuminate\Support\Facades\Storage;
// File size in bytes
$size = Storage::size('file.jpg');
// Last modified timestamp (Unix)
$time = Storage::lastModified('file.jpg');
// MIME type
$mime = Storage::mimeType('file.jpg');
// Absolute path (local driver only)
$path = Storage::path('file.jpg');
Directory operations
use Illuminate\Support\Facades\Storage;
// List files in a directory
$files = Storage::files($directory);
// List files recursively
$files = Storage::allFiles($directory);
// List directories
$directories = Storage::directories($directory);
// Create a directory
Storage::makeDirectory($directory);
// Delete a directory and its contents
Storage::deleteDirectory($directory);
Testing
Use Storage::fake() to test file operations without touching a real disk:
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
test('users can upload an avatar', function () {
Storage::fake('avatars');
$file = UploadedFile::fake()->image('avatar.jpg');
$this->post('/user/avatar', ['avatar' => $file]);
Storage::disk('avatars')->assertExists('avatar.jpg');
Storage::disk('avatars')->assertMissing('other.jpg');
});
Practical example: profile avatar upload
A complete controller that validates the upload, replaces an existing avatar, stores the file, and saves the path to the database:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Storage;
class ProfileController extends Controller
{
public function updateAvatar(Request $request): RedirectResponse
{
$request->validate([
'avatar' => ['required', 'image', 'max:2048'],
]);
$user = $request->user();
// Delete the existing avatar
if ($user->avatar_path) {
Storage::disk('public')->delete($user->avatar_path);
}
// Store the new avatar
$path = $request->file('avatar')->store('avatars', 'public');
// Save the path
$user->update(['avatar_path' => $path]);
return back()->with('status', 'avatar-updated');
}
}
Display the avatar in a template:
<img src="{{ Storage::disk('public')->url($user->avatar_path) }}" alt="Avatar">