laravel/laravel
Storage
Active contributors: Taylor Otwell
Purpose
storage/ is where the framework writes anything it wants to persist between requests but does not commit to source control. Logs, compiled views, sessions, cached files, file uploads — all of it ends up in some subdirectory here. The directory is committed (so a fresh clone has the right structure) but its contents are mostly gitignored.
Directory layout
storage/
├── app/
│ ├── .gitignore # Excludes everything except .gitignore
│ ├── private/
│ │ └── .gitignore
│ └── public/
│ └── .gitignore
├── framework/
│ ├── .gitignore # Excludes compiled, config, services, package manifests
│ ├── cache/
│ │ ├── .gitignore
│ │ └── data/
│ │ └── .gitignore
│ ├── sessions/
│ │ └── .gitignore
│ ├── testing/
│ │ └── .gitignore
│ └── views/
│ └── .gitignore
└── logs/
└── .gitignore # Excludes *.logEvery leaf directory has a .gitignore that excludes everything except itself (the standard *\n!.gitignore pattern). This keeps the directory tree intact in fresh clones without pulling generated files into source control.
Subdirectories
storage/app/
The default file-system disk for Storage::disk('local') and Storage::disk('public'), configured in config/filesystems.php:
'local' => [
'driver' => 'local',
'root' => storage_path('app/private'),
'serve' => true,
'throw' => false,
'report' => false,
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => rtrim(env('APP_URL', 'http://localhost'), '/').'/storage',
'visibility' => 'public',
'throw' => false,
'report' => false,
],| Path | Used by |
|---|---|
storage/app/private/ |
Storage::disk('local') — files private to the application |
storage/app/public/ |
Storage::disk('public') — files served via the /storage symlink |
The php artisan storage:link command creates public/storage → storage/app/public, so anything under storage/app/public/ becomes web-reachable.
storage/framework/
Internal framework workspace.
| Path | Used for |
|---|---|
storage/framework/cache/ |
The file cache store's data directory and lock files |
storage/framework/cache/data/ |
Actual cached payloads |
storage/framework/sessions/ |
The file session driver's storage |
storage/framework/views/ |
Compiled Blade view files |
storage/framework/testing/ |
Scratch directory used by Storage::fake() during tests |
storage/framework/maintenance.php |
Created by php artisan down, removed by php artisan up — public/index.php checks for it |
The default .env.example does not use the file driver for cache or sessions — it points both at the database driver. So storage/framework/cache/ and storage/framework/sessions/ will stay empty unless you switch drivers in .env. storage/framework/views/ always fills up because Blade compilation always uses the filesystem.
storage/logs/
The default log channel (single per config/logging.php) writes here:
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],The daily channel rotates logs into the same directory with a date suffix. storage/logs/.gitignore excludes *.log so log files never get committed.
laravel/pail (the dev log tailer included in composer.json requires) reads from this same directory and parses entries as they arrive.
Generated files (gitignored examples)
| File / pattern | Source |
|---|---|
storage/logs/laravel.log |
Log::* writes (when channel=single) |
storage/logs/laravel-2026-04-30.log |
Log::* writes (when channel=daily) |
storage/framework/views/<hash>.php |
Blade compilation |
storage/framework/cache/data/<a>/<b>/<key> |
Cache::put() calls (when CACHE_STORE=file) |
storage/framework/sessions/<id> |
Session writes (when SESSION_DRIVER=file) |
storage/framework/maintenance.php |
php artisan down |
storage/framework/down |
Same — older Laravel marker |
storage/*.key |
Encryption key files (.gitignore rules) |
storage/pail/ |
php artisan pail UNIX domain socket / runtime |
Permissions
The web server user must be able to write to every subdirectory. On a typical Linux deployment that means:
chown -R www-data:www-data storage
chmod -R 775 storageFor local dev with php artisan serve (or the composer dev script), the user that owns the project is the same user PHP runs as, so no chmod gymnastics needed.
Integration points
config/filesystems.phpmapslocalandpublicdisks here.config/cache.php'sfilestore andconfig/session.php'sfiledriver write here.config/logging.phppoints thesingle,daily, andemergencychannels atstorage/logs/.public/index.phpexplicitly checks forstorage/framework/maintenance.php.vite.config.jsignoresstorage/framework/views/**to prevent rebuild loops.
Entry points for modification
You almost never edit anything inside storage/ by hand. The two cases that come up:
- Resetting state.
php artisan optimize:clearclears compiled views, cached config, cached routes, cached events — equivalent torm -rf storage/framework/views/*and thebootstrap/cache/*.phpfiles. - Inspecting logs.
tail -f storage/logs/laravel.logorphp artisan pailare the two go-tos.
Key source files
| File | Purpose |
|---|---|
storage/app/.gitignore |
Defines what's tracked under app/ |
storage/framework/.gitignore |
Excludes per-framework generated files |
storage/logs/.gitignore |
Excludes *.log |
See Config for the configurations that drive what ends up here, and How to contribute → Debugging for log-driven debugging workflows.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.