gliterd/backblaze-b2
Laravel package that integrates Backblaze B2 cloud storage with the framework's filesystem, so you can upload, store, and serve files through a familiar driver interface with simple configuration and the official B2 SDK.
Install and wire it up in three steps:
composer require gliterd/backblaze-b2
php artisan vendor:publish --provider="Gliterd\BackblazeB2\BackblazeB2ServiceProvider"
Then set credentials in .env (names vary slightly by version — confirm against config/backblaze.php after publishing):
BACKBLAZE_B2_ACCOUNT_ID=your_account_id
BACKBLAZE_B2_APPLICATION_KEY=your_application_key
The package ships a service provider and a BackblazeB2 facade, so a smoke test is one line from php artisan tinker:
use Gliterd\BackblazeB2\Facades\BackblazeB2;
BackblazeB2::listBuckets(); // confirms auth, config, and HTTP stack all work
Where to look first: the package predates good docs. Read src/Client.php (or the equivalent top-level class) first — it maps 1:1 to Backblaze's B2 REST API (b2_authorize_account, b2_list_buckets, b2_get_upload_url, etc.). The bucket and file models next to it are thin wrappers. The README is worth 5 minutes; the source is worth 30.
First realistic use case: offloading user-generated media (avatars, PDFs, exports) from local disk or S3 to B2 because storage cost is the driver. Start with getBucket() + an upload path, not with a full Storage integration — B2 has no first-class Laravel disk driver in this package.
$bucket = BackblazeB2::getBucket('user-uploads');
$file = $bucket->upload('avatars/42.png', fopen($path, 'r'));
// persist $file->getId() alongside the filename
Treat B2 file IDs as the primary key. B2 deletes require both the fileId and the fileName; the SDK models expose both. Store them together in your database rather than reconstructing paths later:
// migration
$table->string('b2_file_id')->nullable();
$table->string('b2_file_name')->nullable();
Wrap the facade in a small service and inject it. The facade is convenient but painful to fake and hides the account/bucket coupling. A one-class wrapper (App\Storage\B2Storage) gives you a single seam for tests, retries, and swapping providers later:
class B2Storage
{
public function __construct(private string $bucketName) {}
public function put(string $path, $stream, array $options = []): array
{
$bucket = BackblazeB2::getBucket($this->bucketName);
$file = $bucket->upload($path, $stream, $options);
return ['id' => $file->getId(), 'name' => $file->getName()];
}
}
Bind it in a service provider, resolve it from the container, and you can $this->mock(B2Storage::class) in feature tests without touching HTTP.
Upload from a queued job, not the request cycle. Uploads are network-bound and occasionally retried; the request should only enqueue:
class PushAvatarToB2 implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function handle(B2Storage $storage): void
{
$storage->put("avatars/{$this->user->id}.png", fopen($this->localPath, 'r'));
}
}
List with prefix + pagination. listFiles() takes a bucket and filters; for anything beyond a few hundred objects you must page with the last returned filename as startFileName. Don't loop listFiles() with no cursor — you'll silently cap at the API's default page size.
Use it for backups, not just user files. A scheduled command that tars a database dump and pushes it with a dated path (backups/2024/06/01.sql.gz) is the lowest-effort, highest-value use of the package. Keep the last N file IDs in a table so a prune job can delete the old ones.
Integration tip — pair with Flysystem if you need Storage::. B2 speaks the B2 API, not S3, so Laravel's s3 driver won't work. Either (a) keep this package behind your own service as above, or (b) add a Flysystem B2 adapter and register a custom disk. Mixing both leads to two auth paths and confused config.
composer conflicts; budget for a fork (repositories + vcs entry) or pick a maintained B2 client. Check composer.json constraints before you commit to it.Gliterd\BackblazeB2\BackblazeB2ServiceProvider and alias BackblazeB2 in config/app.php manually.config:cache will bite you. Env-only credentials read through env() outside of config files return null once cached. Always put env() calls in config/backblaze.php and read config('backblaze.…') everywhere else.upload() path is single-shot. Verify which method handles multipart in the source, and never read a multi-hundred-MB file into a string — pass a stream/filename./. URL-encode when constructing paths or download URLs; don't assume basename() round-trips.allPrivate vs allPublic — if you need signed, time-limited downloads, confirm the SDK exposes b2_get_download_authorization; if it doesn't, you'll be adding that call yourself.MockHandler) if you're exercising the client directly, or fake your own service wrapper if you're not. The package's own tests are a decent crib sheet for real API shapes.{"code": "...", "message": "..."} and the SDK's exceptions occasionally lose the message — you'll spend an hour on "unauthorized" caused by a typo'd key.How can I help you explore Laravel packages today?