laravel-file-vault maintained by jsdevart
Laravel File Vault
Private file storage for Laravel with signed URLs, multi-context organization, and local/S3 support.
Requirements
- PHP 8.2+
- Laravel 11 or 12
Installation
composer require your-vendor/laravel-file-vault
The package registers itself automatically via Laravel's package discovery.
Publish the config file:
php artisan vendor:publish --tag=file-vault-config
Configuration
config/file-vault.php:
| Key | Default | Description |
|---|---|---|
disk |
null (uses filesystems.default) |
Storage disk to use (local, s3, etc.) |
expiry_minutes |
60 |
Signed URL expiry time in minutes |
serve_route |
files.serve |
Route name that serves private files (local disk only) |
Environment variables:
FILE_VAULT_DISK=local
FILE_VAULT_EXPIRY_MINUTES=60
FILE_VAULT_SERVE_ROUTE=files.serve
Usage
1. Create a storage service for your context
Copy stubs/ExampleStorageService.php into your application and adjust it:
// app/Services/Storage/UserStorageService.php
namespace App\Services\Storage;
use YourVendor\LaravelFileVault\Services\BaseFileStorageService;
class UserStorageService extends BaseFileStorageService
{
public function __construct()
{
parent::__construct('user'); // root folder in storage
}
protected function buildBasePath(string $element): string
{
return match ($element) {
'picture' => 'pictures',
'document' => 'documents',
default => $element,
};
}
}
Files will be stored at: {context}/{element_folder}/XX/XX/{uuid}.{ext}
Example: user/pictures/aB/cD/3f2a...uuid.jpg
2. Register your services
In your AppServiceProvider:
use YourVendor\LaravelFileVault\FileStorageRegistry;
use App\Services\Storage\UserStorageService;
public function register(): void
{
FileStorageRegistry::register('user', UserStorageService::class);
}
3. Use in your controllers
use YourVendor\LaravelFileVault\FileStorageRegistry;
// Store a file
$service = FileStorageRegistry::get('user');
$path = $service->store('picture', $request->file('photo')->get(), 'jpg');
$user->update(['photo_path' => $path]);
// Generate a signed URL (expires per config)
$url = FileStorageRegistry::get('user')->getUrl($user->photo_path);
// Delete a file
FileStorageRegistry::get('user')->delete($user->photo_path);
// Check existence
FileStorageRegistry::get('user')->exists($user->photo_path);
4. Display the URL in a view or API response
// In a model accessor (recommended)
public function getPhotoUrlAttribute(): ?string
{
return $this->photo_path
? FileStorageRegistry::get('user')->getUrl($this->photo_path)
: null;
}
Serving private files (local disk)
The package does not provide a controller. You are responsible for implementing the route that streams private files. This gives you full control over middleware, authorization, and response headers.
Reference implementation:
// routes/web.php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Storage;
Route::get('/files/{path}', function (Request $request, string $path) {
if (! $request->hasValidSignature()) {
abort(403);
}
$decoded = base64_decode($path);
if (! $decoded || ! Storage::disk('local')->exists($decoded)) {
abort(404);
}
$filePath = Storage::disk('local')->path($decoded);
$mimeType = mime_content_type($filePath) ?: 'application/octet-stream';
return response()->stream(function () use ($filePath) {
$stream = fopen($filePath, 'rb');
fpassthru($stream);
fclose($stream);
}, 200, [
'Content-Type' => $mimeType,
'Content-Length' => Storage::disk('local')->size($decoded),
'Cache-Control' => 'private, max-age=3600',
'Content-Disposition' => 'inline; filename="'.basename($decoded).'"',
]);
})->name('files.serve')->where('path', '.*');
Make sure the route name matches file-vault.serve_route in your config (default: files.serve).
For S3 (and other cloud disks), getUrl() delegates to the driver's native temporaryUrl() — no route needed.