speicher210/cloudinary-bundle
Symfony bundle that integrates Cloudinary's cloud-based image and video management into your app, providing service configuration, upload and transformation helpers, and template integration for serving optimized, responsive media.
This package is a Symfony bundle, not a Laravel package. Nothing in 0.18.0 changes that: there is still no Laravel service provider, no package auto-discovery, no Blade directives, and no Artisan commands. In Laravel the pragmatic path remains cloudinary-labs/cloudinary-laravel or a thin wrapper around the official cloudinary/cloudinary_php SDK, using this bundle only as a reference for configuration and helper structure.
If you are in Symfony:
composer require speicher210/cloudinary-bundle:^0.18
Note that the bundle now depends on PSL (azjezz/psl) at runtime; if you vendor or fork it, that dependency comes along.config/bundles.php if Flex does not do it:
return [
Speicher210\CloudinaryBundle\Speicher210CloudinaryBundle::class => ['all' => true],
];
config/packages/cloudinary.yaml, reading values from environment variables:
speicher210_cloudinary:
cloud_name: '%env(CLOUDINARY_CLOUD_NAME)%'
api_key: '%env(CLOUDINARY_API_KEY)%'
api_secret: '%env(CLOUDINARY_API_SECRET)%'
secure: true
Verify the exact root key and option names in the bundle's DependencyInjection/Configuration.php before committing; the README and that file are the source of truth.DependencyInjection/Configuration.php for every supported config key and default.Resources/config/services.* for service IDs and aliases, and for the newly autowired Cloudinary class.Command/ classes for console command names and arguments, plus the new tests added for them in 0.18.0.tests/ — the release added tests for the console commands and the Cloudinary factory, which are now the most reliable usage examples available for a package this small.Cloudinary class directly (now autowired by type) and upload a local file:
use Cloudinary\Cloudinary;
public function __construct(private Cloudinary $cloudinary) {}
public function upload(string $path): array
{
$result = $this->cloudinary->uploadApi()->upload($path, [
'folder' => 'uploads',
'resource_type' => 'image',
]);
return [
'public_id' => $result['public_id'],
'url' => $result['secure_url'],
];
}
Persist the public_id and the secure_url, not just the final URL, because public_id is what you need for later transformations, replacement, and deletion.Adoption remains very low, so budget time to read the source. The maintenance signals are better than before — CI refresh, Renovate, static analysis via Mago, and new test coverage — but this is still a small integration layer over the official SDK rather than a fully documented framework component. Check the justfile for the project's own local dev/test commands if you plan to contribute or fork.
Inject by type, not by service ID. As of 0.18.0 the bundle autowires its Cloudinary class, so prefer constructor injection of Cloudinary (or the typed upload/admin APIs) over pulling cloudinary.upload_api-style IDs from the container. This also insulates you from container ID changes. Keep a small application-level interface around it so your domain code does not depend on the bundle directly.
Use the upload API for write paths. Typical day-to-day operations:
$cloudinary->uploadApi()->upload($localPath, $options);
$cloudinary->uploadApi()->upload($remoteUrl, $options);
$cloudinary->uploadApi()->destroy($publicId, ['resource_type' => 'image']);
Always pass explicit folder, resource_type, and upload_preset where appropriate. The "upload without a prefix" path was fixed in 0.18.0, so re-test any code that relied on the previous (buggy) behaviour — particularly uploads where you omitted a prefix or folder and relied on it being blank. Store the full response or at least public_id, resource_type, format, version, and secure_url; transformations change the URL, and version helps with cache busting.
Use the admin API for inventory and cleanup. The admin API is useful for operational tasks: listing resources by prefix or tag, inspecting metadata, bulk deletion, and checking transformation usage. Wrap destructive operations in your own service with dry-run support and confirmation. Note that the bundle's own delete command is now stricter (see Gotchas), which is a good pattern to copy rather than work around.
Prefer named transformations and upload presets over long inline transformation strings. Inline transformations are convenient for Twig output but become unmaintainable. Define presets in Cloudinary or in configuration and reference them by name. This also makes it easier to change image behavior without redeploying every template.
Use the Twig helpers for presentation, not business logic — but re-learn their signature. In 0.18.0 the Twig tags are built from the configured Cloudinary instance and accept transformation options and HTML attributes. A typical template pattern now looks like:
{{ cloudinary_image(public_id, {
transformation: { width: 400, crop: 'fill', fetch_format: 'auto' },
attributes: { alt: 'Profile photo', class: 'avatar', loading: 'lazy' }
}) }}
{{ cloudinary_url(public_id, { transformation: 'thumb', secure: true }) }}
Exact option and attribute keys are authoritative in the Twig extension source. Pass HTML attributes through the helper instead of hand-assembling surrounding markup, and keep the public_id in your entity or model so URLs are generated at render time.
Drive console commands from deployment and maintenance scripts. The bundle exposes commands to upload, inspect, and delete assets. Typical uses:
Run these commands with explicit environment flags. 0.18.0 adds tests for these commands, which is a good place to confirm argument names and output formats. Do not wire irreversible deletion into a deploy hook without a guard.
Integration pattern for Laravel. Still no Laravel service provider. If you want to reuse the bundle's approach in Laravel:
AppServiceProvider that constructs the Cloudinary client from config/services.php / .env.Keep configuration environment-driven. Put CLOUDINARY_URL or the individual cloud name, API key, and API secret in environment variables. Use different Cloudinary accounts or upload presets per environment when possible, so staging uploads do not pollute production media. If the bundle supports a single cloudinary:// URL, prefer that or the explicit keys consistently; do not mix both.
Test around a narrow boundary. Because the bundle exposes SDK clients directly, unit tests become awkward if controllers call them inline. Define an interface such as ImageUploader with upload() and delete(), implement it with the bundle services, and mock the interface in tests. For integration tests, use a dedicated Cloudinary test account and clean up assets by tag or prefix.
Cache generated URLs deliberately. Cloudinary URLs are deterministic, so they can be cached at the application layer. 0.18.0 changes the default URL shape by dropping Cloudinary's analytics query parameter, so any cache keys derived from full URLs are effectively new. Cache the URL, not the binary, and invalidate when the public_id, transformation, or analytics setting changes.
Cloudinary with transformation options and HTML attributes. Existing templates using the old function signatures or positional arguments need rewriting. Grep your templates for the helper names before upgrading.Cloudinary class can collide with your own bindings. If you previously aliased or overrode that class/service ID in your container, the new autowiring may change which definition wins. Run container compilation in CI after upgrading.azjezz/psl instead of PHP built-ins. Check that this is acceptable in your dependency policy and that your PHP version matches what PSL and the updated metadata require.Configuration.php class is the authoritative list of options, defaults, and required values.secure_url for production. Ensure the secure option is enabled so generated URLs use HTTPS. Mixed-content image URLs are an easy regression to miss.version when replacing an asset in place.--force or confirmation in your own command wrapper, and log every deleted public_id. Mirror the bundle's new refusal semantics rather than bypassing them.public_id or use overwrite. Inspect and delete commands may behave differently across resource types; the new command tests are a good reference for intended behaviour.cloudinary/cloudinary_php versions; 0.18.0 refreshed package metadata and dependencies, so constraint bounds may have moved. A stale constraint can force an old SDK with known issues.How can I help you explore Laravel packages today?