Storage::disk
Returns the filesystem instance for a named disk from config/filesystems.php.
Storage::disk(string|null $name = null): Illuminate\Contracts\Filesystem\Filesystem- Added in Laravel 5.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$$nameoptional | string|null= null | A key from the disks array in config/filesystems.php. Null or omitted uses the default disk from filesystems.default. |
Return value
Illuminate\Contracts\Filesystem\FilesystemAn Illuminate\Filesystem\FilesystemAdapter for the named disk. The same instance is returned on subsequent calls within the request.
Errors
InvalidArgumentException when the named disk is not defined in config/filesystems.php, or when its driver is not registered.
Changelog
- PHP 11.xThe local disk's root moved from storage/app to storage/app/private, and the default filesystem disk became local rather than the previous public-facing setup. Paths written by a Laravel 10 application land in a different directory after upgrading.
Storage::disk picks which configured filesystem the following call operates on. Every other method on the facade — put, get, delete — is forwarded to the default disk when you skip it.
What a disk is
An entry in the disks array of config/filesystems.php: a driver (local, s3, sftp, ftp, scoped) plus its settings. Two disks may use the same driver pointed at different roots, which is exactly how local and public differ in a stock application — storage/app/private versus storage/app/public.
When to reach for it
Name the disk explicitly whenever the choice matters: user uploads that must be web-reachable go to public or s3, generated invoices go somewhere private. Relying on the default is fine only when the code genuinely does not care.
Because the API is identical across drivers, a disk name is also the seam for tests: Storage::fake('photos') swaps in a temporary local disk and the code under test is unchanged.
What trips people up
A typo in the disk name is an InvalidArgumentException at call time, not a config error at boot. Resolved disks are memoised for the request, so changing config afterwards has no effect until Storage::forgetDisk() is called. And public only means "reachable over HTTP" after php artisan storage:link has been run on that machine.
Example
<?php
use Illuminate\Support\Facades\Storage;
// No disk named -> filesystems.default, which is 'local' in a stock app.
Storage::put('notes.txt', 'private by default');
// Explicit disks. Same API, different destinations.
Storage::disk('local')->put('invoices/2024-05.pdf', $pdf = 'binary');
Storage::disk('public')->put('avatars/7.png', $png = 'binary');
Storage::disk('s3')->put('exports/report.csv', 'id,total');
// local -> storage/app/private/invoices/2024-05.pdf
// public -> storage/app/public/avatars/7.png (web-visible after storage:link)
echo Storage::disk('local')->path('invoices/2024-05.pdf'), PHP_EOL;
// A disk built at runtime, with no config entry.
$tmp = Storage::build(['driver' => 'local', 'root' => sys_get_temp_dir()]);
var_dump($tmp->put('scratch.txt', 'hello'));When to use it
- Write user avatars to S3 while keeping generated PDF invoices on a private local disk.
- Point a scoped disk at one prefix inside an existing bucket so a tenant cannot reach another's files.
- Swap in Storage::fake('photos') in a test so upload code runs against a temporary directory.
Frequently asked questions
What is the difference between the local and public disks?
They both use the local driver but point at different roots: local writes to storage/app/private and is not reachable over HTTP, while public writes to storage/app/public and becomes web-visible once php artisan storage:link creates the public/storage symlink. Choose per file, based on whether an anonymous visitor should be able to fetch it.
Why do I get "Disk [x] does not have a configured driver"?
The name you passed is not a key in the disks array of config/filesystems.php, or its driver package is not installed — the s3 driver needs league/flysystem-aws-s3-v3 and sftp needs league/flysystem-sftp-v3. A cached config file that predates the new disk causes the same error; run php artisan config:clear.
Can I create a disk without adding it to the config file?
Yes, pass a configuration array to Storage::build(['driver' => 'local', 'root' => '/path/to/root']) and use the returned instance. This is useful for one-off destinations such as a customer-supplied bucket whose credentials live in the database rather than in config.
More examples
One code path, two disks chosen by visibility
The upload logic is written once; only the disk name varies. Storing the disk name alongside the path in the database matters — six months later nothing else in the row tells you which filesystem to read it back from. Note the branch on URL generation: the private disk has no public URL, so it issues a signed temporary one instead.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class DocumentController extends Controller
{
public function store(Request $request): array
{
$disk = $request->boolean('shareable') ? 'public' : 'local';
$path = Storage::disk($disk)->putFile('documents', $request->file('doc'));
// Only the public disk can produce a plain URL.
$url = $disk === 'public'
? Storage::disk($disk)->url($path)
: Storage::disk($disk)->temporaryUrl($path, now()->addMinutes(15));
echo "{$disk}: {$path}", PHP_EOL;
return ['path' => $path, 'disk' => $disk, 'url' => $url];
}
}Faking a disk in a test
fake() replaces the named disk in the container with a local one rooted in a temporary directory, so the application code keeps calling Storage::disk('photos') without knowing. The temporary files are cleared between tests unless persistentFake is used, and assertExists/assertMissing read the fake rather than S3, so no network is touched.
<?php
namespace Tests\Feature;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
class AvatarTest extends TestCase
{
public function test_avatar_is_stored_on_the_photos_disk(): void
{
Storage::fake('photos');
$this->post('/avatar', ['avatar' => UploadedFile::fake()->image('me.jpg')]);
Storage::disk('photos')->assertExists('avatars/me.jpg');
Storage::disk('photos')->assertMissing('avatars/other.jpg');
echo Storage::disk('photos')->size('avatars/me.jpg'), PHP_EOL;
}
}
Discussion