Open-Source Wikis

/

Laravel

/

Systems

/

Storage

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 *.log

Every 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/storagestorage/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 uppublic/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 storage

For 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.php maps local and public disks here.
  • config/cache.php's file store and config/session.php's file driver write here.
  • config/logging.php points the single, daily, and emergency channels at storage/logs/.
  • public/index.php explicitly checks for storage/framework/maintenance.php.
  • vite.config.js ignores storage/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:clear clears compiled views, cached config, cached routes, cached events — equivalent to rm -rf storage/framework/views/* and the bootstrap/cache/*.php files.
  • Inspecting logs. tail -f storage/logs/laravel.log or php artisan pail are 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.

Storage – Laravel wiki | Factory