Updated June 2026. Tested on Laravel 13 and PHP 8.4.
Laravel's Storage API lets you handle files without caring where they actually live. The same code works whether the files are on the local disk or on Amazon S3, because you talk to a "disk" rather than to a path. Here we build a small upload and download flow and keep a record of each file in the database.
A table to track files
Storing the file is half the job. We also keep a row with its details so we can serve it back later.
php artisan make:model FileEntry -m
public function up(): void
{
Schema::create('file_entries', function (Blueprint $table) {
$table->id();
$table->string('disk')->default('public');
$table->string('path');
$table->string('original_name');
$table->string('mime');
$table->timestamps();
});
}
Configure the disk
Disks are defined in config/filesystems.php. The public disk stores files in storage/app/public, which becomes web reachable once you run the storage link.
php artisan storage:link
To move everything to S3 later, you change FILESYSTEM_DISK in .env and your code does not change at all.
Upload a file
In the controller, validate the upload, store it, and save the record. The store method puts the file on the disk and returns the generated path.
use App\Models\FileEntry;
use Illuminate\Http\Request;
public function store(Request $request)
{
$request->validate([
'file' => ['required', 'file', 'max:10240'], // up to 10 MB
]);
$file = $request->file('file');
$path = $file->store('uploads', 'public');
FileEntry::create([
'disk' => 'public',
'path' => $path,
'original_name' => $file->getClientOriginalName(),
'mime' => $file->getClientMimeType(),
]);
return redirect()->back()->with('status', 'File uploaded.');
}
store('uploads', 'public') saves the file under uploads/ on the public disk with a random, safe name, and hands you back the path to keep.
Download a file
Because we saved the disk and path, serving the file back is one line. Storage::download sets the right headers and streams it.
use Illuminate\Support\Facades\Storage;
public function download(FileEntry $entry)
{
return Storage::disk($entry->disk)->download($entry->path, $entry->original_name);
}
If instead you want to show the file inline, for example an image, use the public URL.
$url = Storage::disk($entry->disk)->url($entry->path);
Listing them in a view
@foreach ($entries as $entry)
<a href="{{ route('files.download', $entry) }}">{{ $entry->original_name }}</a>
@endforeach
Handy Storage methods
The same facade does everything else you need.
Storage::disk('public')->exists($path);
Storage::disk('public')->put('path/file.txt', $contents);
Storage::disk('public')->get($path);
Storage::disk('public')->delete($path);
The point of the Storage API is that none of this changes when you switch where files live. Develop against the local disk, push to S3 in production, same code. Questions welcome in the comments.
All comments ()
No comments yet
Be the first to leave a comment on this post.