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

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.

View on GitHub
Deep Wiki
Context7

Getting Started

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:

  1. Install the bundle and the Cloudinary PHP SDK, pinning the version because 0.18.0 contains two BC breaks:
    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.
  2. Register the bundle in config/bundles.php if Flex does not do it:
    return [
        Speicher210\CloudinaryBundle\Speicher210CloudinaryBundle::class => ['all' => true],
    ];
    
  3. Configure credentials in 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.
  4. Look first at:
    • 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.
    • The Twig extension class for exact function/filter names and signatures — these changed in 0.18.0, so read the current source, not blog posts or older examples.
    • 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.
  5. First use case: inject the bundle's 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.

Implementation Patterns

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:

  • Upload a directory of seed or editorial assets after deployment.
  • Inspect an asset by public ID to verify format, dimensions, and delivery type.
  • Delete assets by public ID or prefix during cleanup.

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:

  1. Bind a singleton in AppServiceProvider that constructs the Cloudinary client from config/services.php / .env.
  2. Bind the upload and admin API objects from that client.
  3. Expose Blade directives or helper functions yourself if you want tag rendering; replicate the new 0.18.0 tag-options/attributes structure rather than the old fixed-signature helpers.
  4. Register Artisan commands that call your wrapper service. Alternatively, use the official Cloudinary Laravel package and skip this bundle entirely. Do not try to register a Symfony bundle in a Laravel application; the bundle's extension, compiler passes, and Twig extensions will not run.

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.

Gotchas and Tips

  • 0.18.0 has two BC breaks; read them before upgrading.
    • Deletion is now refused without a prefix or a resource, or with an empty one. Any script, command wrapper, or deploy hook that previously passed an empty prefix to "delete everything" will now fail — by design. Audit all delete call sites, pass an explicit prefix or resource type, and adjust your own wrappers so the failure mode is a clear error rather than a silently ignored flag.
    • The Twig tags are now built from the configured 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.
  • URLs changed by default. Cloudinary's analytics parameter is now left out unless you opt in. This shifts cache keys, so expect a one-time CDN/application cache miss after deploy. If your CSP, signed-URL verification, or URL allowlisting logic matched on that parameter, fix it now.
  • This is still Symfony-only. The Twig extension, console commands, and container wiring are not usable in Laravel without significant adaptation. The Twig dependency alone is a blocker for Blade-based applications. No Laravel support was added in this release.
  • Autowiring the 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.
  • New runtime dependency on PSL. The bundle now uses 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.
  • Confirm configuration keys against the source. Documentation is sparse for a package with this adoption level, and dependency metadata was refreshed in 0.18.0. The Configuration.php class is the authoritative list of options, defaults, and required values.
  • Do not expose API secrets in Twig or frontend code. Only the cloud name and upload preset are safe in client-side uploads. Signed uploads and admin operations must stay server-side.
  • Use 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.
  • Transformations are part of the URL contract. Changing a transformation name or its parameters produces a different URL and a different cache entry. Prefer named transformations, and include the asset version when replacing an asset in place.
  • Private and authenticated assets need signed URLs. The generic URL and tag helpers may not produce signed delivery URLs for restricted assets. Check the extension's options — the 0.18.0 signature changes are a good moment to verify this — and fall back to the SDK's signing methods when needed.
  • Deletion is irreversible and broad. Admin API deletion by prefix or tag can remove more than intended. Require an explicit --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.
  • Upload failures are not always exceptions. Inspect the SDK response for error structures and HTTP status. 0.18.0 fixed the public ID shown for a failed upload, so error output now identifies the asset you actually attempted — log it. Add retries only for transient failures, not for authentication errors.
  • Large uploads need different handling. Prefer remote URL uploads, streaming, or direct browser-to-Cloudinary uploads with signed parameters for large files. Server-side uploads of large media can hit PHP memory and execution limits.
  • Console commands may not be idempotent. Uploading the same file repeatedly can create duplicates unless you set a deterministic 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.
  • Service IDs and visibility can change. Autowiring is now available, which is the safer path. If you still rely on container IDs rather than type-based injection, pin the bundle version and add integration tests around container compilation.
  • Watch version compatibility. Verify the bundle's supported Symfony and 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.
  • Low adoption, but improving maintenance. Still a small project, so expect little community documentation for edge cases. The 0.18.0 work — CI/PHPUnit refresh, Renovate, Mago static analysis, PSL, new tests — reduces the risk of silent breakage but does not make it a widely exercised component. Vendor or fork it if you depend on it heavily, and keep your own wrapper layer so replacing it later is straightforward.
  • The tag helpers render HTML. Treat the output as trusted markup, but always supply alt text and dimensions where possible — now easier since attributes are passed explicitly. Do not pass user-controlled transformation strings directly into helpers without validation.
  • Invalidate downstream caches after replacement or deletion. Cloudinary's CDN caches by URL; your application cache may also hold old URLs. Deleting an asset does not automatically purge every cached derivative in your own systems, and the dropped analytics parameter means the old and new URL forms will not share an entry.
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.
jinas/dhivatar
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