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 Flysystem Cloudinary Laravel Package

codebar-ag/laravel-flysystem-cloudinary

Flysystem adapter that plugs Cloudinary into Laravel's filesystem, letting you store, retrieve, and manage uploaded images and files through the familiar Storage facade. Simple configuration and seamless integration with Laravel's native upload, URL generation, and file handling APIs.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the adapter and its transitive Flysystem driver:

composer require codebar-ag/laravel-flysystem-cloudinary

Optionally publish the package config (it just exposes the driver defaults):

php artisan vendor:publish --tag="flysystem-cloudinary-config"

Add credentials to .env. The Cloudinary SDK can read a single CLOUDINARY_URL, which is easier to keep in sync than three separate vars:

CLOUDINARY_URL=cloudinary://API_KEY:API_SECRET@CLOUD_NAME

Register a disk in config/filesystems.php:

'cloudinary' => [
    'driver' => 'cloudinary',
    'key'       => env('CLOUDINARY_KEY'),
    'secret'    => env('CLOUDINARY_SECRET'),
    'cloud_name'=> env('CLOUDINARY_CLOUD_NAME'),
    'secure',   => true,
    'prefix'    => env('CLOUDINARY_PREFIX', 'app'),
],

First use case: wire it up as your default storage disk (or a named disk next to local/s3) and move one upload path — a user avatar or a CMS image — onto it. Don’t migrate everything at once; Cloudinary’s model (public IDs, transformation URLs, resource types) behaves differently enough from S3 that you want a single flow working end-to-end first:

$path = $request->file('avatar')->store('avatars', 'cloudinary');
// avatars/<random>.jpg — the public ID, not a filesystem path
$url = Storage::disk('cloudinary')->url($path);

The two files to read first are the disk config you wrote and the underlying flysystem-cloudinary adapter source (in vendor/saucebase-dev/flysystem-cloudinary/). The Laravel package is a thin service provider; almost all behavior lives in the adapter, so debugging usually means reading that class, not this repo.

Implementation Patterns

Upload from a request. Keep using Laravel’s store/storeAs so you stay disk-agnostic — that’s the main payoff of the Flysystem wrapper:

$path = $request->file('photo')->storeAs(
    'users/'.$user->id,
    $request->file('photo')->hashName(),
    'cloudinary'
);

Because Cloudinary folders are just prefixes on the public ID, users/42/abc.jpg becomes the public ID app/users/42/abc.jpg if you set prefix to app.

Generated content. Storage::disk('cloudinary')->put('reports/q1.pdf', $bytes) works, but note the resource-type caveat below for non-image files.

URLs in Blade / API resources. Use Storage::disk('cloudinary')->url($publicId) for the plain CDN URL. For anything with transformations, don’t fight the adapter — compose the URL with the Cloudinary SDK or cloudinary_url() helper (if cloudinary-labs/cloudinary-laravel is also installed) and pass both the public ID and the transformation:

public function avatarUrl(int $size = 200): string
{
    $id = $this->avatar_public_id;
    return cloudinary()->image($id)
        ->resize(ResizeMode::fill, $size, $size)
        ->toUrl();
}

Store the public ID on your models, never a full URL. URLs rot when you change the cloud name, prefix, or transformations.

Multiple disks. Define separate disks when you need different Cloudinary folders or accounts (e.g. cloudinary for user uploads, cloudinary-media for editorial assets). Pointing several disks at the same cloud with different prefix values is the standard multi-tenant pattern.

Testing. Storage::fake('cloudinary') swaps in an in-memory Flysystem disk, so your upload tests run offline. Assert on the stored public ID rather than on a full URL, since the fake won’t produce real CDN URLs.

Interop with cloudinary-labs/cloudinary-laravel. If the project already uses that SDK for uploads, keep Flysystem for the "storage" surface (put/get/delete) and use the SDK for transformations, eager processing, and management APIs. They share the same credentials — don’t duplicate the env vars.

Gotchas and Tips

  • Everything is treated as image by default. Cloudinary distinguishes image, video, and raw resource types. Uploading a PDF or ZIP through the plain Flysystem adapter will often land as an image resource and be rejected or delivered incorrectly. Either use a dedicated disk for raw assets or use the Cloudinary SDK’s uploadApi()->upload(..., ['resource_type' => 'raw']) directly.
  • url() does not apply transformations. The Flysystem interface has no concept of them, so the returned URL is the untransformed original. If your code assumes Storage::url() gives you a resized thumbnail, it won’t — build those URLs separately.
  • Overwrites won’t bust the CDN. Cloudinary caches aggressively. Uploading a new file to the same public ID leaves the old version served at the same URL until you either change the ID or invalidate via uploadApi()->invalidate(). In practice, prefer unique filenames (hashName() or a ULID) over overwriting.
  • prefix is not a directory. It’s glued onto the public ID. Change it after launch and every previously stored ID breaks. Pick it once and put it under change control.
  • Deletes are asynchronous CDN-wise. delete() removes the asset on Cloudinary, but edge caches may serve it for a while. For user-initiated deletion with a legal expectation of removal, also call invalidate().
  • directoryExists() and listing are approximate. Cloudinary’s folder semantics are derived from public IDs; allFiles()/allDirectories() can be slow or paged on large accounts. Treat the disk as a write/read-by-ID store, not a browsable filesystem.
  • Env vs. config cache. php artisan config:cache is fine with this package as long as you reference env() in config/filesystems.php (as shown) and not inside the disk array elsewhere. A common failure is credentials that work locally and break in production because they were read at runtime rather than baked into the cached config.
  • Quotas and transformation add-ons. Free and low tiers will throttle bandwidth and transformations before storage. If you see intermittent 401s, check the dashboard’s usage, not your credentials — Cloudinary returns 401 for exceeded limits on some endpoints.
  • Debugging. Enable LOG_CHANNEL=stderr and set debug => true in the disk config (if supported by the adapter) or add a Http\Client middleware in a service provider to log the underlying Cloudinary API calls. Most “the file uploaded but I can’t see it” reports trace back to a resource-type mismatch or a wrong prefix.
  • Testing edge case. Storage::fake() does not exercise Cloudinary-specific validation, so a green suite doesn’t prove your production uploads will succeed. Keep one integration test (or a manual smoke check) against a staging cloud for image, video, and raw uploads before each release.
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.
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings