mhetreramesh/flysystem-backblaze
A Flysystem adapter for Backblaze B2 cloud storage that lets PHP and Laravel applications upload, download, list, and delete files through a familiar filesystem API. Configure B2 buckets and use Flysystem streams, metadata, and file operations in your app.
Check the constraints before anything else. This package hasn't been touched since 2016, so its composer.json almost certainly pins league/flysystem to ^1.0 and depends on an old Backblaze B2 SDK (Guzzle 5/6 era). Run composer show mhetreramesh/flysystem-backblaze and inspect vendor/mhetreramesh/flysystem-backblaze/composer.json before installing. If you're on Laravel 9+, stop here — Laravel ships Flysystem 3 and this adapter's AdapterInterface no longer exists.
Install only in a compatible project (Laravel ≤8 / Flysystem 1):
composer require mhetreramesh/flysystem-backblaze
Collect credentials from the Backblaze B2 console: accountId, applicationKey, and the bucket ID (not the bucket name — they're different values and easy to swap). Create a key scoped to the bucket with listFiles, readFiles, writeFiles, deleteFiles capabilities.
First use case: point a Laravel disk at it and store user uploads or nightly database dumps. Register a custom driver in a service provider (see next section), then:
Storage::disk('b2')->put('backups/'.now()->format('Y-m-d').'.sql.gz', $gz);
Look first at src/ in the vendor directory for the exact adapter class name and constructor signature — the README is the only documentation you'll get, and it may not match the code.
Custom Laravel disk via Storage::extend. This is the day-to-day integration. In config/filesystems.php:
'b2' => [
'driver' => 'backblaze',
'accountId' => env('B2_ACCOUNT_ID'),
'appKey' => env('B2_APPLICATION_KEY'),
'bucketId' => env('B2_BUCKET_ID'),
],
Then in AppServiceProvider::boot():
Storage::extend('backblaze', function ($app, $config) {
$client = new \BackblazeB2\Client($config['accountId'], $config['appKey']);
$adapter = new \Mhetreramesh\Flysystem\BackblazeAdapter($client, $config['bucketId']);
return new \League\Flysystem\Filesystem($adapter);
});
Confirm the class names in vendor/ — the constructor may want a bucket name rather than an ID.
Everyday operations go through the normal Storage API once the disk is registered: put, putFile, putFileAs, get, exists, delete, files, allFiles. Streams work if the adapter implements writeStream/readStream; verify with a 50 MB file early, not in production.
Path conventions. B2 is flat object storage with /-delimited keys. Prefix everything by environment and type (prod/uploads/…, staging/…) so a single bucket can serve multiple apps and a mis-scoped delete can't nuke everything. Use date-sharded paths for backups so listContents stays cheap.
Backup command pattern. A scheduled artisan command that dumps MySQL, gzips it, and pushes to B2 is the sweet spot for this package. It's a small file count, sequential writes, and no URL/visibility requirements — all the adapter's weak spots are avoided.
Read-through caching. Wrap the adapter in League\Flysystem\Cached\CachedAdapter with an in-memory or Laravel cache store for metadata-heavy flows (listContents on large prefixes is slow over the B2 API).
Testing. Don't hit B2 in unit tests. Keep a local disk for feature tests, and put adapter behaviour behind a thin repository/service class you can fake. Reserve real-bucket tests for a single integration test run manually.
AdapterInterface with FilesystemAdapter and split packages. There's no drop-in fix; the realistic migration is the next bullet.league/flysystem-aws-s3-v3 with endpoint => https://s3.<region>.backblazeb2.com, use_path_style_endpoint => true, region = your bucket's region, and you get a maintained adapter with multipart uploads, presigned URLs, and full Laravel support. For a greenfield project this package is not worth the risk.memory_limit / 2.composer why-not is your friend; there is often no clean resolution.put($path, $contents, ['visibility' => 'public']) is silently ignored. Public URLs only work if the bucket itself is public; otherwise Storage::url() will be wrong or return false, and you need a download authorization token instead.listContents pagination. B2 returns at most 1000 entries per list call. Old adapters frequently forget to loop, so allFiles() silently truncates. If file counts matter, page manually or verify the adapter handles nextFileName.\Exception, so Storage::exists() can throw instead of returning false. Wrap read paths in try/catch and log the underlying B2 error body.has()/delete() round trip is broken, you get orphaned objects you'll pay to store forever.write, read, delete, listContents, getMetadata) and you control the error mapping and multipart handling.composer update can't yank it out from under you.How can I help you explore Laravel packages today?