0

Public vs private disk — php artisan storage:link

Beginner5 min read·lv-23-003
security

Concept

Custom filesystem drivers allow integrating any storage backend — Cloudflare R2, Backblaze B2, Google Cloud Storage, DigitalOcean Spaces, or a completely custom implementation — into Laravel's Storage facade.

Flysystem adapters: Laravel's filesystem layer is built on league/flysystem. Most cloud storage services have a Flysystem adapter package. You register the adapter in a service provider, and it becomes a new disk driver.

Registration: Storage::extend(string $driver, callable $callback) in a service provider's boot(). The callback receives $app and $config (from config/filesystems.php). Return a Illuminate\Filesystem\FilesystemAdapter wrapping a Flysystem filesystem.

Community adapters:

  • league/flysystem-aws-s3-v3: AWS S3 / R2 / Spaces (S3-compatible).
  • league/flysystem-google-cloud-storage: Google Cloud Storage.
  • spatie/flysystem-dropbox: Dropbox.
  • Most cloud providers offer an S3-compatible API — use the S3 adapter with a custom endpoint.

Cloudflare R2 (S3-compatible): Configure the S3 driver with R2's endpoint URL. No extra adapter needed.

Custom adapter: Implement League\Flysystem\FilesystemAdapter interface for completely custom backends (custom CMS asset storage, corporate FTP, etc.).

Code Example

php
<?php
// Cloudflare R2 — uses S3 driver with custom endpoint
// config/filesystems.php
'r2' => [
    'driver'                  => 's3',
    'key'                     => env('R2_ACCESS_KEY_ID'),
    'secret'                  => env('R2_SECRET_ACCESS_KEY'),
    'region'                  => 'auto',
    'bucket'                  => env('R2_BUCKET'),
    'url'                     => env('R2_URL'),     // your custom domain or R2 public URL
    'endpoint'                => env('R2_ENDPOINT'), // https://accountid.r2.cloudflarestorage.com
    'use_path_style_endpoint' => true,
],

// Use it exactly like any other disk
Storage::disk('r2')->put('uploads/file.txt', $content);
Storage::disk('r2')->temporaryUrl('private/doc.pdf', now()->addHour());

// Google Cloud Storage via custom driver
// composer require superbalist/flysystem-google-cloud-storage

class FilesystemServiceProvider extends \Illuminate\Support\ServiceProvider
{
    public function boot(): void
    {
        \Illuminate\Support\Facades\Storage::extend('gcs', function($app, $config) {
            $storageClient = new \Google\Cloud\Storage\StorageClient([
                'keyFilePath' => $config['key_file'],
            ]);
            $adapter = new \Superbalist\Flysystem\GoogleStorage\GoogleStorageAdapter(
                $storageClient,
                $storageClient->bucket($config['bucket'])
            );
            return new \Illuminate\Filesystem\FilesystemAdapter(
                new \League\Flysystem\Filesystem($adapter),
                $adapter,
                $config
            );
        });
    }
}

// config/filesystems.php — use the custom driver
'gcs' => [
    'driver'   => 'gcs',
    'key_file' => env('GCS_KEY_FILE'),
    'bucket'   => env('GCS_BUCKET'),
],

// Usage
Storage::disk('gcs')->put('backups/data.json', $data);
$url = Storage::disk('gcs')->url('images/photo.jpg');