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

Laravel Backblaze B2 Laravel Package

gliterd/laravel-backblaze-b2

Laravel filesystem driver for Backblaze B2 cloud storage. Upload, download, and manage files on B2 through Laravel's familiar Storage facade, with support for large-file uploads, signed URLs, and seamless integration into your existing Laravel app.

View on GitHub
Deep Wiki
Context7

Getting Started

gliterd/laravel-backblaze-b2 is a thin Laravel service-provider wrapper around the gliterd/backblaze-b2 PHP SDK. It gives you a container-bound SDK client and a facade; it does not give you a Laravel filesystem disk. That single fact shapes almost everything below.

Requirements and setup:

composer require gliterd/laravel-backblaze-b2

You need a Backblaze B2 account, a bucket, and an application key (not your account password). Note the account ID and application key ID separately — B2 auth uses the key ID, and mixing these up is the most common first-run failure.

Register the provider (Laravel 5.5+ should auto-discover; on 5.0–5.4 add it manually in config/app.php):

Gliterd\BackblazeB2\LaravelServiceProvider::class,

Publish the config and set credentials in .env:

php artisan vendor:publish --provider="Gliterd\BackblazeB2\LaravelServiceProvider"
B2_ACCOUNT_ID=...
B2_APPLICATION_KEY=...

Where to look first, in order:

  1. The published config file — the exact key names and whether a default bucket ID is supported live here.
  2. src/LaravelServiceProvider.php and the facade — confirm what the facade accessor resolves to (the raw B2Client).
  3. The upstream gliterd/backblaze-b2 README — the wrapper adds almost nothing; all the real API surface (bucket(), upload(), files(), deleteFile(), etc.) is in the SDK.

First use case — get a bucket handle and put bytes in it:

$bucket = B2::bucket($bucketId);

$file = $bucket->upload('reports/2024-06.pdf', $pdfContents);
// persist $file->getId() / $file->getFileName() for later retrieval

Start there rather than with a Storage-disk abstraction: it's the shortest path to a working upload and matches how the package expects to be used.

Implementation Patterns

Wrap the facade behind your own service. Because the package exposes a concrete SDK client, injecting B2::class directly couples your app to an unmaintained library. Define an interface you control:

interface ObjectStore {
    public function put(string $key, string $contents): string;
    public function get(string $fileId): string;
    public function delete(string $fileId): void;
}

class B2ObjectStore implements ObjectStore
{
    public function __construct(private Bucket $bucket) {}

    public function put(string $key, string $contents): string
    {
        return $this->bucket->upload($key, $contents)->getId();
    }
    // ...
}

Bind Bucket in a service provider by resolving B2::bucket(config('...')) once, so bucket lookup isn't repeated per call.

Bucket as a value object. Resolve the bucket handle once (in a provider or a memoized helper) and pass it around. Every file operation goes through the bucket object — there is no top-level "put this file" on the client.

Model the two-level identity. B2 has both a file ID (stable, server-assigned) and a file name (the path you choose). Uploads return the object with both. Store the file name for human-facing lookups and the file ID for reliable delete/download, since names can be re-used across versions.

Offload to the queue. Uploads are synchronous HTTP against B2's upload URL; a 20 MB PDF will hold a PHP-FPM worker for seconds. Do this in a job:

class UploadReport implements ShouldQueue
{
    public function handle(ObjectStore $store): void
    {
        $id = $store->put("reports/{$this->report->id}.pdf", $this->report->pdf);
        $this->report->update(['b2_file_id' => $id]);
    }
}

Chunking for large files. The B2 API requires large uploads to be split into parts. Check whether the SDK does this automatically for your version; if not, implement the start-upload / upload-part / finish-upload loop yourself and keep a part-size constant (B2 recommends ~100 MB parts, with the last part smaller). Treat this as an explicit code path for anything above a few tens of MB.

Backups and scheduled exports. The package works fine inside a console command: resolve the bucket, stream a dump, upload. Pair it with a retention job that lists files and deletes old prefixes — but paginate (see gotchas).

Testing. The facade is swappable: B2::shouldReceive('bucket')->andReturn($fakeBucket). Better, bind a fake ObjectStore in the test container so none of your tests ever touch the SDK.

Gotchas and Tips

It is not a Laravel Storage disk. There is no Flysystem adapter here, so Storage::disk('b2')->put(...), Storage::url(), and friends are unavailable. If your code is already written against Storage, either refactor to your own store interface or add a third-party Flysystem B2 adapter — don't try to make this package imitate one.

Laravel version ceiling. This package targets Laravel 5.x and predates L6+. Expect Composer constraint conflicts on modern Laravel; if you're on a current framework, pull gliterd/backblaze-b2 directly and write a ten-line service provider. Forking is usually cheaper than fighting the constraints.

Bucket ID vs bucket name. Most B2 operations want the bucket ID, not the friendly name. Resolve it once via listBuckets() and put it in config; don't pass names into bucket() and wonder why lookups fail.

Singleton token staleness. The client caches its auth token for the process lifetime. Long-running queue workers can hold an expired token after a restart-free deploy — restart workers on deploy, or resolve a fresh client per job. This is a classic "works in tinker, 401s in Horizon" bug.

Retries are on you. B2 returns 429 and 503 under load, and the SDK surfaces these as exceptions. Wrap uploads in a bounded retry with exponential backoff; don't let a transient 503 fail a user-facing request.

Upload URLs are single-use. Each upload begins by requesting a fresh upload URL from B2. Don't cache or reuse them across calls; the SDK handles this internally, but it's why "one client, many uploads" is fine while "one upload URL, many files" is not.

Paginate file listings. files()-style calls return a page and a continuation token. A naive loop over a large bucket will silently miss files (or hammer the API). Follow the next-file token until it's null.

Filename encoding. File names are sent in headers/URLs; spaces, unicode, and slashes need consistent URL-encoding. Test with a real-world name containing a space and an accent before shipping.

Config caching. If your config file reads env() directly, php artisan config:cache will bake values in — fine, but remember to re-cache after changing credentials, and never call env() outside config files in your own wrapper.

Debugging auth failures. A 401 almost always means: wrong key type (master key vs application key), account ID/key ID swapped, or a key lacking permission on that bucket. Log the raw response body — B2's error messages are specific even when the SDK's exception wrapping is not.

Cost awareness. B2 charges for downloads and transactions but is cheap on storage; accidental get() loops in views are a real bill. Cache downloaded bytes locally or serve from a CDN in front of B2 rather than proxying through PHP on every request.

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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route