v13.37.0 implementation. For basic usage, see File storage. For how providers are loaded, see Package discovery.
Separate driver names from disk names
The first argument toStorage::extend() is a driver name. The argument to Storage::disk(), on the other hand, is a disk name defined in filesystems.disks. You can define multiple disks that use the same driver.
FilesystemManager::resolve() looks for a custom creator matching the configured driver and, if one exists, calls it before any built-in driver. Using local or s3 as an extension name replaces the existing creator, so choose a name specific to your package.
Register only the creation logic in the provider
The following minimal example reuses Laravel’s standard local driver creator to illustrate the registration path. If you only need local storage, the regularlocal driver is sufficient. When you provide a custom backend, replace this with logic that builds a Flysystem adapter and wraps it in Laravel’s FilesystemAdapter, as in the official documentation example.
runningInConsole(), so the driver is available to HTTP requests, Artisan, and queue workers. Also, don’t make a provider whose only job is registering the extension a deferred provider.
The callback receives the application and the entire configuration of the disk being resolved. Validate the configuration and create clients inside the callback, and don’t perform file operations in boot(). The createLocalDriver() call in the example may create a directory when the disk is created, so keeping registration time and creation time separate matters.
Users add the configuration to the disks array in config/filesystems.php.
This example shows only the creation path for local storage. Serving files through
serve and providing temporary URLs are out of scope. If your custom adapter supports URL generation, visibility, read-only mode, or similar features, check each implementation and any required dependency packages. Don’t assume a feature is enabled just because you pass a configuration key.Named disks are retained after creation
disk('reports') doesn’t call the callback every time. The Manager’s get() prefers an already created instance in its internal $disks array. Only when the disk hasn’t been created yet does it go through resolve(), and the result is retained under the disk name as the key.
As a result, doing either of the following in the same process doesn’t automatically update an already created disk:
- Changing
filesystems.disks.reportswithconfig(). - Calling
Storage::extend()again for the same driver name.
FilesystemServiceProvider also registers the Manager itself as a singleton. In long-running processes, don’t assume disks are always recreated for each request or job.
Re-resolve only the disks you need after configuration changes
When you change configuration and verify the result within the same process, treat updating the configuration and removing the retained disk as separate steps.forgetDisk() removes the given name from the Manager’s retention array. The next disk() call reads the current configuration again. In the version examined, purge() is also implemented as removing the given name. It doesn’t call any connection-closing method on a custom backend.
For configuration changes and package updates in production, rebuild the configuration cache as part of your normal deployment and restart long-running workers as well. The configuration cache and the in-process disk retention array are separate things.
Use build for temporary configuration
If you don’t want to change shared named configuration, you can use an on-demand disk.build() calls resolve() directly with the given configuration and doesn’t add the result to the named disk retention array. The custom driver still needs to be registered, but each call goes through the creator. Even when you use dynamic storage locations, don’t design the system so that users can freely choose storage locations or connection targets from their input.
Test the creation path separately from fake
Storage::fake('reports') uses the Manager’s set() to replace that disk with a local disk for testing. It’s convenient for testing application file operations, but it doesn’t verify that the package’s custom creator is registered and executed correctly.
After loading ArchiveServiceProvider in your package test environment, verify the following separately.
For a custom external backend, separate creation tests that don’t use the network from connection tests in an explicitly prepared verification environment. Don’t treat a passing fake as evidence of compatibility with the real service.
Long-term maintenance checklist
- Document the driver name, configuration keys, return value API, and the handling of exceptions and
throwas a public specification. - Use an extension name that doesn’t collide with built-in drivers or other packages.
- Don’t resolve or use disks just by booting the provider.
- When configuration changes, distinguish between the configuration cache, the Manager’s retention array, references held by calling code, and other processes.
- Continuously verify creation, re-resolution, and actual file operations for each supported Laravel version.
Primary sources consulted
- Laravel official documentation: Custom filesystems
- FilesystemManager: disk, build, resolve, forgetDisk, purge, extend
- FilesystemServiceProvider: singleton registration of the Manager
- Storage facade: replacement with a local disk via fake
- Laravel official tests: creating and removing disks, custom closures