carlosocarvalho/flysystem-cloudinary
Flysystem adapter that brings Cloudinary storage to Laravel's filesystem abstraction. Use the familiar Storage facade to upload, retrieve, and manage images and files on Cloudinary, combining cloud-based media delivery with Laravel's simple, driver-based file handling in your PHP app.
Architecture fit. Flysystem is the right seam to evaluate: it gives Laravel a single Storage contract across local, S3, and Cloudinary, which is exactly the abstraction that prevents vendor-specific upload code from spreading through controllers and jobs. The adapter's value proposition is real. However, Cloudinary is not a generic object store, and the adapter is a lossy translation layer between two different data models:
resource_type ∈ image/video/raw, type ∈ upload/private/authenticated, public_id), where slashes in public_id simulate folders but have no independent existence.hero.jpg); Cloudinary stores public_id = hero plus a separate format. Any naive bidirectional mapping between path and public_id is a round-trip hazard for listContents → read/delete.url() returns a plain string; transformation parameters are outside the contract. Teams expecting Storage::disk('cloudinary')->url($path) to emit resized/optimized variants will be disappointed and will end up writing Cloudinary-specific code anyway — partially defeating the abstraction.directoryExists, visibility, and mimeType semantics are approximations. Cloudinary has no POSIX permissions; "visibility" maps at best to upload type.Integration feasibility. High. This is a standard custom-driver registration: a service provider calling Storage::extend('cloudinary', ...), a disk entry in config/filesystems.php, credentials from env, and league/flysystem v3 already present in any Laravel 9+ app. No framework patching, no build tooling, no frontend surface. Realistic effort for a competent Laravel engineer: half a day to a working prototype, two to five days to production-grade with tests.
Technical risk. The dominant risk is not code quality — it is adoption and maintenance signal. 22 stars and a single-namespace vendor package means effectively zero community backpressure. The stated last release of 2026-04-04 is future-dated relative to normal package lifecycles and should be verified against Packagist and the actual repo tags before anyone treats it as actively maintained. There is no evidence in the supplied material of a CI matrix, a Flysystem contract test suite (the league/flysystem-adapter-test-utilities harness), or declared Laravel/PHP version bounds. Treat the high numeric score as a heuristic on packaging quality, not as a diligence substitute.
Key questions for the maintainer / spike:
cloudinary/cloudinary_php major is required, and which Laravel and PHP versions are in the CI matrix?league/flysystem-adapter-test-utilities? Which contract tests are skipped, and why?public_id ↔ path mapping done for files with extensions, and is it stable across write → listContents → delete?PublicUrlGenerator / TemporaryUrlGenerator, and how are Cloudinary signed/private delivery URLs surfaced?move/copy implemented — native Cloudinary rename or copy+delete?listContents use the Admin API per call? If so, what is the pagination and rate-limit behavior on a 10k-asset prefix?deleteDirectory implemented — delete_resources_by_prefix? Can it be made opt-in/guarded?resource_type/type in listings, or does it hide them (which would make deletion ambiguous)?Stack fit. Laravel 9/10/11 with Flysystem 3 and PHP 8.1+ is the target envelope; verify against this package's composer constraints before committing. Assume the Cloudinary PHP SDK is a transitive dependency. This fits cleanly into an app that already uses Storage for user uploads, but it is a media disk, not a general-purpose filesystem — do not point logs, exports, or backups at it.
Migration path. Do not swap the default disk.
cloudinary) and keep local/s3 as the default for non-media.MediaStore interface wrapping the disk, so the Flysystem adapter is one implementation and the Cloudinary SDK is a fallback for transformation URLs. This caps the blast radius if the package is abandoned.public_id/resource_type/type alongside your existing file record.Compatibility. Pin the adapter and the Cloudinary SDK to exact versions in composer.json. Add a CI job that runs the Flysystem adapter contract test utilities against a sandbox Cloudinary account, plus a nightly smoke test that writes, lists, and deletes a scratch asset — this catches silent API drift far earlier than production. Verify url() output against your CDN/transformation requirements before designing front-end components around it.
Sequencing.
url()/temporaryUrl() meet requirements.MediaStore wrapper, config, credential handling via secrets manager.If the decision gate fails, fall back to the Cloudinary SDK directly behind the same MediaStore interface — the interface costs little and is the real risk mitigation.
Maintenance. Bus factor of one is the headline. Budget for the possibility that your team becomes the maintainer. Practical mitigations: vendor the adapter into a first-party namespace (MIT/Apache permitting), or keep the MediaStore boundary thin enough that replacing it is a one-sprint job. Track upstream releases but do not assume they will happen.
Support. Cloudinary support covers the Cloudinary API, not this adapter. Package issues have no SLA and likely no triage. Your on-call runbook must therefore be written against the Cloudinary Admin/Upload API and the Cloudinary console, not against the adapter's documentation.
Scaling. The scaling profile differs sharply from S3-style stores:
listContents on a large prefix means paginated Admin API calls, which are rate-limited per account (limits vary by plan). Any UI that "browses" the disk will be slow and rate-limit-prone. Cache listings; prefer your own database as the index of record and treat Cloudinary as the storage layer.Failure modes to design against.
public_id.resource_type/type silently no-ops, leaving assets that appear deleted in the UI but remain billable and publicly reachable. Verify deletes return the expected result, don't fire-and-forget.deleteDirectory blast radius: if implemented via prefix deletion, one malformed prefix can wipe a large swath of assets. Gate it behind an explicit allowlist and never expose it to user-driven paths.public_id may serve stale cached content. Know the invalidation path before you promise "update in place."Ramp-up. Low for application developers — if they know Storage::put/get/delete, they are productive in an hour. Moderate for whoever owns infra: they must learn Cloudinary's resource model, transformation syntax, Admin API quotas, and the billing console, none of which the adapter abstracts. Plan roughly one engineer-week of operational documentation and runbook work, plus the ongoing cost-governance owner. The adapter makes the happy path cheap; the expensive parts of Cloudinary were never inside Flysystem's scope.
How can I help you explore Laravel packages today?