Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Flysystem Cloudflare Bundle Laravel Package

softavis/flysystem-cloudflare-bundle

A Symfony bundle that integrates Flysystem with Cloudflare R2 object storage, making it easy to configure R2 as a filesystem for uploads, downloads, and file management through familiar Flysystem APIs.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check before installation. This package is a Symfony bundle (-bundle suffix, Symfony DI extension + config tree). Laravel has no Symfony Flex bundle auto-discovery, so the bundle wrapper — service definitions, Extension class, config tree — is inert in a Laravel app. The reusable asset is the Flysystem adapter class underneath. Confirm early whether the maintainer also publishes a standalone softavis/flysystem-cloudflare adapter package (the bundle almost certainly depends on it); if it does, install that directly and skip this package entirely.

Minimal steps (spike, timeboxed to one day):

  1. Pin exactly — no caret at 0.1.x:
    composer require "softavis/flysystem-cloudflare-bundle:0.1.2"
    
  2. Read vendor/softavis/flysystem-cloudflare-bundle/ — find the classes registered in Resources/config/services.yaml and the adapter constructor signature. That is the API surface that actually matters.
  3. Check the installed dependency tree for a second Flysystem major or a conflicting Symfony HTTP client:
    composer why league/flysystem
    composer show -t softavis/flysystem-cloudflare-bundle
    
  4. Stand up a scratch R2 bucket plus an API token scoped to it, and note the account endpoint (https://<account-id>.r2.cloudflarestorage.com) and region: auto.

First use case — bypass the bundle. Do not try to boot the Symfony bundle inside Laravel. Instead register the adapter as a custom disk driver in an app-owned service provider, exposing it behind Storage::disk('r2'):

// app/Providers/FilesystemServiceProvider.php
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\Storage;
use League\Flysystem\Filesystem;
use Softavis\FlysystemCloudflare\CloudflareAdapter; // verify actual namespace

public function boot(): void
{
    Storage::extend('r2', function ($app, array $config) {
        $adapter = new CloudflareAdapter(
            $config['bucket'],
            $config['account_id'],
            $config['access_key_id'],
            $config['secret_access_key'],
            $config['endpoint'] ?? null,
        );

        return new FilesystemAdapter(new Filesystem($adapter), $adapter, $config);
    });
}
// config/filesystems.php
'r2' => [
    'driver' => 'r2',
    'bucket' => env('R2_BUCKET'),
    'account_id' => env('R2_ACCOUNT_ID'),
    'access_key_id' => env('R2_ACCESS_KEY_ID'),
    'secret_access_key' => env('R2_SECRET_ACCESS_KEY'),
],

Then exercise the four call patterns you will actually ship — put, get, temporaryUrl, delete — against the bucket before writing any domain code.

Where to look first, honestly. The best reference implementation is not this package — it is league/flysystem-aws-s3-v3 pointed at an R2 endpoint with region: auto, which is the well-trodden path and takes ~10 lines of config. Build that working disk first as a permanent fallback, then evaluate this adapter against it on the specific gaps (signed URL ergonomics, multipart tuning, public/custom-domain URLs).

Implementation Patterns

Pattern: adapter-only consumption, bundle ignored. The bundle's DI wiring is dead weight in Laravel; consume only the adapter class, either by installing it directly (preferred) or by referencing the class from the bundle package. Never invoke the Symfony Extension or config tree — Laravel's service container does not know about them, and trying to bridge them creates more surface area than the adapter itself.

Pattern: app-owned driver behind a contract test. Own the registration in FilesystemServiceProvider, and add a Pest/PHPUnit contract test that runs the same assertions against both your R2 disk and the AWS-S3-adapter fallback disk. This keeps the swap-out live and turns an upstream break into a failing test rather than a production incident:

it('stores and retrieves files', function (string $disk) {
    Storage::disk($disk)->put('probe.txt', 'ok');
    expect(Storage::disk($disk)->get('probe.txt'))->toBe('ok');
    Storage::disk($disk)->delete('probe.txt');
})->with(['r2', 'r2-aws-fallback']);

Pattern: thin internal facade over Storage. Domain code should only ever call Storage::disk('assets') (or a FileStore interface wrapping it), never adapter-specific methods. The moment a controller calls $adapter->somethingCloudflareSpecific(), the config-change swap becomes a refactor.

Pattern: env-parity disks. Keep local for dev and r2 for staging/prod, but drive both through the same domain-facing interface. This surfaces visibility and temporaryUrl divergence (see gotchas) in CI rather than in prod.

Pattern: signed-URL generation in one place. If you serve private assets, centralise temporaryUrl / presigned logic in a service that switches between the R2 adapter and the AWS-S3 fallback based on a config flag. The two adapters have subtly different signatures; isolate that.

Pattern: multipart for large uploads. R2 is S3-compatible; if the adapter exposes multipart configuration, tune part size for your typical asset (e.g. 10–25 MB chunks for video). If it does not expose it, the AWS S3 adapter does — another data point for the fallback path.

Workflow: one-day evaluation spike. (1) Read the 0.1.1→0.1.2 diff directly — release notes are a bare compare URL, so the diff is the changelog. (2) Stand up both disks. (3) Exercise upload/read/delete/visibility/temporaryUrl on each. (4) Quantify what the Softavis adapter gives you that S3-adapter-on-R2 does not. (5) Only promote if the delta is concrete.

Gotchas and Tips

This is a Symfony bundle, not a Laravel package. The strongest signal in the repo name is that it targets Symfony. Laravel will not auto-register its service provider, will not discover its DI extension, and will not read its config tree. If you install it expecting a filesystems driver to appear, it will not. Extract the adapter class explicitly (see Getting Started).

Pre-1.0 semver means patch releases can break. At 0.1.x, 0.1.3 is permitted to change a constructor signature. Always pin to "0.1.2" exactly; never ^0.1 — that resolves breaking changes under the guise of patch bumps.

The 0.1.2 release is opaque. There are no release notes, just a compare link. This is a process signal, not a feature signal. Read https://github.com/softavis/flysystem-cloudflare-bundle/compare/0.1.1...0.1.2 before upgrading, every time.

R2 is S3-compatible, so the "need" for this package is narrow. The first-party league/flysystem-aws-s3-v3 with an R2 endpoint and region: auto is the best-supported path. This adapters only justifies itself if it adds something the S3 adapter lacks — presigned-URL convenience, public-bucket URL generation, multipart tuning, or Cloudflare-Images-specific helpers. Verify which Cloudflare product it targets (R2 vs Images vs Stream); the release notes do not say.

Credential/endpoint misconfigures surface as opaque 403s at runtime, not boot. Add a health-check command that does a head (or exists) on a sentinel object and runs in your deploy pipeline:

if (! Storage::disk('r2')->exists('.healthcheck')) {
    throw new RuntimeException('R2 disk unreachable or misconfigured');
}

Visibility and temporaryUrl diverge between local and r2. Local dev will happily return a public URL for a private object; R2 will not. Same for directory-listing consistency. If a test passes on local and 403s on r2, assume visibility semantics, not a bug in your code.

Transitive dependency conflicts are the common failure mode. A pre-1.0 Symfony-oriented package pulling Symfony components into a Laravel app is a classic source of version conflicts with Laravel's own Symfony dependencies. Run composer why on any Symfony packages the bundle brings in before committing.

Bus-factor and abandonment risk are real at this size. Single maintainer, thin release communication, no evident CI matrix or security policy. Mirror the repo internally (or pin a commit hash) so an upstream force-push or deletion is not an outage. If no one on your team can read the adapter source, that is the deciding argument against adoption at 0.1.x.

Extension point — if you do adopt it: wrap the adapter in an app-owned decorator implementing a small FileStore interface. That gives you a single seam to swap in the AWS S3 adapter (or a different provider) without touching domain code, and a single place to add observability, retries, and the health check above.

Bottom line for a Laravel developer: the default path is the AWS S3 adapter on R2. Treat this package as an evaluated-not-default option: pin it, spike it against a real bucket, and only adopt it if it demonstrably adds capability you need — behind your own driver registration and contract test so removal stays a config change.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums