Document Management Best Practices for Package Maintainers
Package maintainers publish code along with repository documents, package metadata, files included in each release, and sometimes a separate documentation site. These…
When a Laravel request cannot save, find, or serve a file, trace how Laravel handles that file through its filesystem abstraction. This layer lets code use the same storage methods with different destinations, such as local directories and cloud services. A disk is one configured destination: it specifies a storage driver and settings such as a root directory or bucket.
Laravel normally treats a storage path as relative to the selected disk’s root. The same path string can therefore refer to different locations on different disks. To diagnose a failure, identify the disk Laravel used and trace the path relative to it. Check whether the write returned false or threw an exception, then investigate local permissions or public access separately.
Storage::put(...) uses the application's configured default disk. Storage::disk('public')->put(...) selects the public disk explicitly. Laravel documents disk definitions and default-disk selection in its file storage documentation.
When a file appears in the wrong place or Laravel cannot find a disk, compare the failing call with the disk settings Laravel is using:
$defaultDisk = config('filesystems.default');
$diskConfig = config("filesystems.disks.{$defaultDisk}");
Check whether the code names a disk explicitly. If it does, inspect that disk’s configuration instead of assuming the default applies. Confirm that the named disk exists and has the expected driver and settings. Laravel’s default filesystem configuration shows how disk definitions and the default disk are structured.
A deployment can also use different configuration from a developer’s local environment. After Laravel caches configuration, requests and Artisan commands do not load .env directly. Read settings through config() in application code, and check the values supplied to the deployment and the configuration cache behavior when a disk unexpectedly points to another destination.
Storage methods treat the path you pass as a location inside the selected disk’s configured root. For example, Laravel resolves reports/monthly.csv from the selected disk’s root, independently of the project directory. Laravel’s path() method can show the resolved filesystem path for a local disk; for cloud storage, a path may instead be relative to a bucket.
Use the disk and relative path from the failing code:
$disk = Storage::disk('local');
$relativePath = 'reports/monthly.csv';
$exists = $disk->exists($relativePath);
$resolvedPath = $disk->path($relativePath);
Call path() only when the disk uses the local driver. Check the disk’s configured root and compare it with the resolved path. A manually constructed absolute path may point somewhere different from the location Laravel checks.
Laravel’s version also matters when the local root was not explicitly configured. The Laravel 12 upgrade guide documents a change: for applications without an explicit local disk definition, the default root changed from storage/app in earlier releases to storage/app/private. If files seem to have disappeared after an upgrade, inspect the effective root before moving or recreating them.
A failed Laravel write does not always throw an exception. By default, put returns false when a write fails. The disk’s throw option makes Laravel raise an UnableToWriteFile exception when a write fails. The report option logs the underlying exception without throwing it. The write failure options determine what the application observes.
Check the return value rather than treating the absence of an exception as proof that the write succeeded:
$disk = Storage::disk('local');
$path = 'diagnostics/probe.txt';
try {
$written = $disk->put($path, 'storage test');
if (! $written) {
// The write failed without throwing.
}
$exists = $disk->exists($path);
} catch (\Throwable $exception) {
// Record the failure using the application's normal logging policy.
}
For diagnosis, enable throw when the code should handle a write failure as an exception, or enable report when logging the underlying exception is more useful. Follow the application’s error-handling policy when changing either setting. After the write, check exists() on the same disk and with the same relative path. A check against another disk does not verify the original write.
For a local disk, the PHP or web-server process needs permission to write to the resolved directory and its parent directories. A successful shell command under your own account does not confirm that the web-server process has the same access. Laravel’s deployment guidance says the web-server process owner needs write permission to storage and bootstrap/cache.
Check the resolved root’s ownership and permissions as the identity running the application. Then test a write through the same disk. Avoid broad permission changes as a first fix: they can hide a path or ownership mismatch without identifying why the application could not write.
Visibility settings control whether local files and directories are public or private. They do not give the process permission to write to a parent directory it cannot access. Laravel’s filesystem documentation gives local examples of 0644 for public files and 0755 for public directories, and 0600 for private files and 0700 for private directories. Treat those modes as visibility mappings, not a general remedy for every permission error.
A browser may fail to retrieve a file that exists in storage. For Laravel’s local public disk, files live in storage/app/public; web access depends on a symbolic link from public/storage to that directory. Laravel documents php artisan storage:link for creating the link.
First check the file through the disk abstraction. When exists() returns true on the expected disk, investigate the public disk configuration, URL, and link separately from the write. When the file does not exist, check the selected disk and relative path. Then review the write result and local permissions.
Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.
Take a look at vroni.com