Storage::put, get, delete, url, temporaryUrl
Concept
Public vs private storage is a fundamental file storage design decision. Laravel's built-in local public disk stores files in storage/app/public/. They're not directly web-accessible until php artisan storage:link creates a symbolic link from public/storage → storage/app/public/.
public disk: Files accessible via Storage::url(). Only use for files that ALL visitors should access: user avatars, product images, public downloads. Never store sensitive files here.
local disk (default): Files in storage/app/. NOT web-accessible. Use for private files, temp files, processing artifacts.
storage:link: php artisan storage:link creates public/storage as a symlink. Called once during setup/deploy. Without it, files on the public disk have no web-accessible URL. The command also shows all configured links.
Custom link paths: In config/filesystems.php, 'links' array maps symlink source → target: public_path('uploads') => storage_path('app/uploads').
Temporary URLs for private files: For private S3/R2/GCS files, use Storage::temporaryUrl() which generates a signed URL valid for a limited time. For local private files, serve them through a controller that checks authorization.
Serving private local files through a controller:
return response()->file(Storage::path($privatePath));This reads the file from storage and streams it to the response — secure because it goes through your authorization middleware.
Code Example
<?php
// config/filesystems.php
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app'), // storage/app/ — not web accessible
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'), // storage/app/public/
'url' => env('APP_URL') . '/storage', // http://app.com/storage/
'visibility' => 'public',
],
'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'), // CDN URL if configured
],
],
'links' => [
public_path('storage') => storage_path('app/public'), // default link
// Add custom links:
// public_path('downloads') => storage_path('app/downloads'),
],
// Storage link setup (one-time, run on deploy)
// php artisan storage:link
// Store PUBLIC file (world-accessible)
$path = $request->file('avatar')->store('avatars', 'public');
// File: storage/app/public/avatars/uuid.jpg
// URL: http://app.com/storage/avatars/uuid.jpg
$url = Storage::disk('public')->url($path);
// Store PRIVATE file
$path = $request->file('contract')->store('contracts', 'local');
// File: storage/app/contracts/uuid.pdf
// No direct URL — must serve through controller
// Serving private files through authorization
class ContractController extends Controller
{
public function download(Request $request, Contract $contract)
{
$this->authorize('download', $contract); // check permission
$path = Storage::path($contract->file_path); // full filesystem path
return response()->file($path, [
'Content-Disposition' => 'attachment; filename="' . $contract->original_name . '"',
]);
}
}