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

Cloudinary Bundle Laravel Package

misteio/cloudinary-bundle

A Symfony bundle that integrates Cloudinary’s image and video upload, transformation, and delivery services into your PHP application, with configuration for cloud credentials and asset handling.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: this is a Symfony bundle, not a Laravel package. It has no ServiceProvider, no config/cloudinary.php, no publishable assets, and Composer will not auto-register it in a Laravel app. It is a thin DependencyInjection wrapper around the official cloudinary/cloudinary_php SDK — 8 stars, 0 dependents, and no recent activity. Treat it as "a config file plus a service alias," because that's all it is.

If you are actually building in Laravel, do not pull this in. Use cloudinary-labs/cloudinary-laravel (maintained, ships a facade, a filesystem disk driver, and a config file) or bind the raw SDK yourself. If you have inherited a Symfony app using this bundle, the day-to-day API below is what you'll touch.

Where to look first

  1. DependencyInjection/Configuration.php — the canonical list of config keys (cloud_name, api_key, api_secret, upload_prefix, secure, cdn_subdomain, etc.). Anything not listed there you cannot set via YAML.
  2. Resources/config/services.yml — shows the service id you inject (typically cloudinary / misteio_cloudinary) and which SDK class it aliases.
  3. The upstream SDK docs (Cloudinary\Api, Cloudinary\Uploader, Cloudinary::cloudinary_url). The bundle adds nothing on top, so SDK docs are the real reference.

First use case — the 90% path: upload a file, persist the public_id, render a transformed URL.

// Symfony: constructor-inject the service
public function __construct(private \Cloudinary\Api $cloudinary) {}

$result = $this->cloudinary->upload('avatar.jpg', [
    'folder'          => 'users/avatars',
    'public_id'       => 'user_' . $user->getId(),
    'overwrite'       => true,
    'invalidate'      => true,
    'transformation'  => ['width' => 400, 'height' => 400, 'crop' => 'fill', 'gravity' => 'face'],
]);

$user->setAvatarPublicId($result['public_id']); // store the ID, not the URL

Laravel equivalent if you skip the bundle — bind the SDK once and move on:

// app/Providers/CloudinaryServiceProvider.php
$this->app->singleton(\Cloudinary\Cloudinary::class, fn () => new \Cloudinary\Cloudinary([
    'cloud' => [
        'cloud_name' => config('services.cloudinary.cloud_name'),
        'api_key'    => config('services.cloudinary.api_key'),
        'api_secret' => config('services.cloudinary.api_secret'),
    ],
    'url' => ['secure' => true],
]));

Implementation Patterns

Store public_id, derive everything else. The single most important habit: persist public_id (plus resource_type and type when not image/upload) in your own schema. URLs, transformations, and deletions are all functions of that ID. Storing an absolute secure_url bakes in a CDN hostname and a transformation version you will later regret.

// URL generation is pure string building — no API call, no latency
$url = cloudinary_url($user->avatar_public_id, [
    'width' => 200, 'height' => 200, 'crop' => 'thumb', 'gravity' => 'face',
    'fetch_format' => 'auto', 'quality' => 'auto',
]);

Use fetch_format=auto / quality=auto (f_auto,q_auto). This is the highest-leverage one-line change you can make: Cloudinary serves WebP/AVIF to browsers that accept it and picks a quality that's visually indistinguishable. Do this before you optimize anything else.

Wrap the SDK behind your own interface. The bundle exposes the global-ish SDK class, which makes it awkward to mock and easy to scatter throughout controllers. Define App\Contracts\ImageStorage with upload(), delete(), url(), and implement it over Cloudinary. This also means the eventual migration off this abandoned bundle is a one-class change.

interface ImageStorage {
    public function upload(UploadedFile $file, string $folder): string; // returns public_id
    public function delete(string $publicId): void;
    public function url(string $publicId, array $transform = []): string;
}

Queue the upload, not the URL. Uploads hit Cloudinary's API (hundreds of ms, occasionally seconds for video). Do them in a job after the request returns, and give your DB a placeholder state (avatar_status = 'processing'). URL generation is synchronous and cheap — never queue it.

Laravel disk integration. If you'd rather stay idiomatic, register a custom filesystem disk so Storage::disk('cloudinary')->put(...) and ->url() work with $request->file('photo')->store('photos', 'cloudinary'). cloudinary-labs/cloudinary-laravel gives you this out of the box; hand-rolling it is ~40 lines around upload() and cloudinary_url().

Deletion is two-phase. deleteAssets only removes the asset; existing URLs may still resolve from CDN edge caches until invalidate: true propagates (and invalidation is a rate-limited, sometimes paid feature). Plan for "deleted but still visible for a few minutes."

Signed uploads for browser-direct (unsigned presets only for public content). If you let the browser upload directly, do not embed your API secret. Use an unsigned upload preset scoped to a folder, or generate a signature server-side. The bundle has no opinion here — it's straight SDK config (upload_preset, signature).

Gotchas and Tips

  • Maintenance risk is the headline. 8 stars, 0 dependents, thin wrapper. Pin the exact version in composer.json, and assume you may need to replace it with a direct cloudinary/cloudinary_php dependency at any time. The interface pattern above is your insurance.
  • Config keys are allow-listed. Anything not in Configuration.php cannot be passed via bundle config (e.g. per-environment upload presets, cname). Work around it by injecting parameters directly into the SDK constructor yourself.
  • url vs secure_url. The bundle's secure flag defaults can bite you in production behind HTTPS — mixed-content warnings on images. Explicitly set secure: true in config and confirm you're using secure_url, not url.
  • public_id is not a filename. If you pass photo.jpg, Cloudinary strips the extension for images but keeps it for resource_type: raw. Overwriting the same public_id with a different resource_type does not replace the old asset — you now have two assets under one ID path. Always include resource_type and type in delete/lookup calls when they aren't the defaults.
  • Transformations create derived assets, and derived assets bill. A loop generating w_100, w_101, … will happily create thousands of variants. Constrain widths to a fixed set (a srcset ladder like 320/640/1280/1920) rather than arbitrary values.
  • Naming a transformation parameter wrong fails silently. Cloudinary ignores unknown URL params — you get an un-transformed image and no error. Sanity-check by comparing the generated URL against the transformation reference; malformed transforms are the #1 "it's broken" cause.
  • Eager transformations are slow and expensive. Use them only for a known small set (thumbnails, hero image). Rely on lazy/on-demand for the long tail.
  • Debugging: the SDK doesn't log by default. Catch \Cloudinary\Api\Exception\ApiError and log $e->getMessage() plus the response http_code and the public_id you attempted. For local failures, dump the request payload before the call — most bugs are a wrong resource_type or a missing upload_preset.
  • Extension point: the only meaningful one is replacing the service alias in your services.yaml with your own class that composes the SDK. There are no events, no middleware, no hooks — don't go looking for them.
  • If you're on Laravel, don't fight the framework. Every hour spent making a Symfony bundle behave in Laravel is an hour better spent on cloudinary-labs/cloudinary-laravel or a 20-line service provider. The underlying SDK is the same; the bundle contributes nothing you can't write yourself in an afternoon.
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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
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