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

yoelpc4/laravel-cloudinary

A Laravel integration for Cloudinary that simplifies uploading, managing, and delivering images and videos through Cloudinary’s API. Configure credentials, generate transformations and URLs, and handle media storage within familiar Laravel conventions.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and wire it up (5 minutes):

composer require yoelpc4/laravel-cloudinary
php artisan vendor:publish --tag=cloudinary-config   # tag name: check README

Add a disk to config/filesystems.php:

'cloudinary' => [
    'driver' => 'cloudinary',
    // credentials are usually read from config/cloudinary.php, not here
],

Set credentials in .env — Cloudinary's own SDK accepts a single CLOUDINARY_URL (cloudinary://API_KEY:API_SECRET@CLOUD_NAME), which the driver almost certainly reads. If not, use the discrete CLOUDINARY_CLOUD_NAME / CLOUDINARY_API_KEY / CLOUDINARY_API_SECRET trio and confirm the key names against the published config.

Where to look first:

  1. config/cloudinary.php after publishing — this is where the credential mapping, default folder, and upload params (upload_preset, secure, resource_type) live.
  2. The service provider — to see the exact Storage::extend('cloudinary', ...) driver name. If it's not cloudinary, your disk config is wrong and you'll get a confusing "Driver [] is not supported" error.
  3. README's supported-operations table — treat this as the contract. Flysystem adapters vary wildly in which of url(), temporaryUrl(), visibility(), directoryExists() actually work. Don't assume S3 parity.

First use case — user avatars:

$path = $request->file('avatar')->store('avatars', 'cloudinary');
// or with a deterministic public ID
Storage::disk('cloudinary')->putFileAs('avatars', $request->file('avatar'), "user-{$user->id}");

$url = Storage::disk('cloudinary')->url($path);

Store the returned path/public ID in the database — not a full URL — so you can regenerate delivery URLs with different transformations later without a migration.

Implementation Patterns

Keep the disk out of your domain code. Inject Illuminate\Filesystem\FilesystemManager or type-hint a small MediaStorage interface that wraps Storage::disk('cloudinary'). Given the package's size (33 stars, single maintainer), an interface gives you a clean swap to S3 + imgproxy or Cloudinary's own SDK if maintenance stalls.

Treat "path" as a Cloudinary public ID. Cloudinary has no real directories — folders are just a prefix on the public ID (avatars/user-42), and resource_type/type are separate dimensions the disk abstraction flattens. Practical consequence: store the full public ID including folder and, if you support multiple formats, the format too. photo.jpg and photo.png collide once the extension is stripped.

Transformations are the reason to use Cloudinary — plan for them. The Flysystem disk usually returns the plain delivery URL, not a transformed one. Two clean options:

// A. Store public ID, build URLs with the Cloudinary SDK helper or a URL builder
$url = cloudinary_url($publicId, ['width' => 400, 'height' => 400, 'crop' => 'fill']);

// B. Use named transformations defined in the Cloudinary dashboard
$url = cloudinary_url($publicId, ['transformation' => 't_avatar_sm']);

Option B is strongly preferred: named transformations keep URL logic out of PHP, are editable without deploys, and are easier to audit against quota. Use eager transformations on upload for your 2–3 most common sizes so the first page view doesn't pay the derivation cost.

Browser-direct uploads for anything large. Don't proxy 50 MB videos through PHP. Get a signature server-side, upload straight to Cloudinary from the client, and persist the returned public_id:

$timestamp = time();
$signature = app(\Cloudinary\Cloudinary::class)->uploadApi()->signature([
    'timestamp' => $timestamp,
    'folder'    => "tenants/{$tenant->id}/videos",
]);
return response()->json(compact('signature', 'timestamp'));

This bypasses the driver entirely for writes but is the pattern that actually scales; use the disk for reads/URLs and admin operations.

Private assets: if the driver exposes temporaryUrl(), it's backed by Cloudinary's private/authenticated delivery with a signed expiry. Verify it against your plan — authenticated delivery for raw files (PDFs, ZIPs) behaves differently from images and is a common source of 401s in production while working fine in staging.

Multi-tenant isolation: prefix every public ID with tenants/{$id}/. Deleting a tenant means the Admin API (delete_resources_by_prefix) — the Flysystem deleteDirectory() may not map to that, so confirm or call the Admin API directly.

Development/production split: keep FILESYSTEM_DISK=local locally and switch via env. Nothing about Cloudinary degrades gracefully offline, and tests that depend on a live cloud will be flaky.

Gotchas and Tips

  • It is not S3. Cloudinary is a media pipeline, not a blob store. Arbitrary binary uploads (backups, exports, signed PDFs) belong on S3. Reserve this disk for images and video, with raw as an escape hatch you validate per use case.
  • exists() is expensive. Depending on implementation it may hit the Admin API, which is rate-limited (roughly 500 requests/hour on the free tier). Never call it inside a loop or a Blade partial. Cache presence in your own DB column instead.
  • Deletes are cached at the edge. Storage::delete() removes the asset but the CDN copy can persist until invalidation — and CDN invalidation is a paid feature on some plans. If a user deletes an avatar, either accept the stale URL or append a version/v parameter to force a fresh fetch.
  • Config caching bites. After php artisan config:cache, any env() call outside config/*.php returns null. If the driver reads env('CLOUDINARY_URL') directly in its own code path, you must run config:cache in deploy and make sure the published config is what's actually used. Symptom: works in php artisan serve, 401s in production.
  • Two credential sources. The Laravel package config and the Cloudinary PHP SDK singleton can diverge (e.g. you configure the disk but call cloudinary_url() with no SDK credentials). Pick one source of truth — usually a single config/cloudinary.php that the service provider also feeds to the SDK.
  • Extension/format ambiguity. Cloudinary derives format from the file; the public_id has no extension. If you reconstruct paths from DB values that include .jpg, lookups silently miss. Normalize on write.
  • Async video uploads. A video put() can return a public ID while the asset is still pending. Transformation URLs 404 until processing completes. Use the upload response's status or a notification_url webhook rather than assuming success.
  • Testing: Storage::fake('cloudinary') swaps in a local temp disk, so your upload/delete flow tests pass — but they validate nothing about transformations, delivery URLs, or credentials. Add one thin integration test (or a scheduled smoke check) that uploads a 1×1 PNG and asserts a 200 on a transformed URL.
  • Pin the version and read the changelog. Low star count + a single maintainer means an unpinned ^1.0 can surprise you. Lock the minor and watch the Flysystem major version constraint — Flysystem 3 API changes broke most community adapters and this one is small enough that it may lag a Laravel release.
  • Extension point: if the driver disappoints, the escape hatch is to drop it and use the official cloudinary/cloudinary_php SDK directly behind your own MediaStorage service. That's ~80 lines and removes the dependency risk entirely — worth prototyping early rather than after you've spread Storage::disk('cloudinary') calls across the codebase.
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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle