Looking to hire Laravel developers? Try LaraJobs

laravel-file-vault maintained by jsdevart

Description
Private file storage for Laravel with signed URLs and multi-context.
Author
Last update
2026/04/13 05:14 (dev-main)
License
Downloads
24

Comments
comments powered by Disqus

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.