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

Cloudder Laravel Package

jrm2k6/cloudder

Cloudder is a Laravel wrapper for the Cloudinary PHP SDK, offering a simple facade to upload, transform, tag, and delete images and videos in the cloud. It streamlines media management in Laravel apps with an easy, expressive API for Cloudinary operations.

Deep Wiki
Context7

Getting Started

Install and register. The package predates Laravel 5.5 auto-discovery, so Composer alone won't wire it up:

composer require jrm2k6/cloudder

Then in config/app.php:

'providers' => [
    // ...
    Jrm2k6\Cloudder\CloudderServiceProvider::class,
],
'aliases' => [
    // ...
    'Cloudder' => Jrm2k6\Cloudder\Facades\Cloudder::class,
],

Publish the config and set credentials.

php artisan vendor:publish --provider="Jrm2k6\Cloudder\CloudderServiceProvider"

This drops config/cloudder.php with cloud_name, api_key, api_secret (and usually an upload_preset). Depending on the version you resolve, those may be hardcoded strings rather than env() lookups — if so, edit the file to read CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, and CLOUDINARY_API_SECRET and add them to .env. Never commit the secret.

Where to look first. Open vendor/jrm2k6/cloudder/src/Cloudder.php and read the method signatures before anything else. This wrapper is thin, and the return types ($this vs. array) differ between tags. Reading ~200 lines saves you an hour of guessing.

First use case — server-side upload from a form.

use Cloudder;

public function store(Request $request)
{
    $request->validate(['image' => 'required|image|max:5120']);

    try {
        Cloudder::upload($request->file('image')->getRealPath(), null, [
            'folder'    => 'avatars',
            'width'     => 400,
            'height'    => 400,
            'crop'      => 'fill',
            'quality'   => 80,
            'fetch_format' => 'auto',
        ]);

        $result   = Cloudder::getResult();      // array: public_id, secure_url, format, bytes...
        $publicId = Cloudder::getPublicId();    // string, use this as your DB reference

        return redirect()->back()->with('url', $result['secure_url']);
    } catch (\Exception $e) {
        report($e);
        return back()->withErrors('Upload failed.');
    }
}

Store public_id, not the URL. URLs are derivable; public IDs are the durable handle for deletes, renames, and tag operations.

Implementation Patterns

Store public IDs, render URLs on demand. Persist $result['public_id'] plus whatever metadata you care about (format, bytes, width, height). Generate display URLs at read time so you can change transformations without a data migration:

$thumb = Cloudder::show($user->avatar_public_id, [
    'width' => 120, 'height' => 120, 'crop' => 'thumb', 'gravity' => 'face',
]);

Isolate Cloudinary in a service class. The facade carries mutable state (uploadedResult, publicId), so calling it from controllers and jobs interchangeably makes ordering bugs likely. Wrap it:

class ImageService
{
    public function upload(UploadedFile $file, string $folder): array
    {
        Cloudder::upload($file->getRealPath(), null, ['folder' => $folder]);
        $result = Cloudder::getResult();

        return [
            'public_id' => $result['public_id'],
            'format'    => $result['format'],
            'bytes'     => $result['bytes'],
        ];
    }

    public function delete(string $publicId): void
    {
        Cloudder::delete($publicId);
    }
}

Now swapping the archived wrapper for the official SDK is a one-file change instead of a grep across the codebase.

Queue heavyweight work. Uploading large images or triggering eager transformations synchronously will blow past PHP's max_execution_time. Dispatch a job:

UploadImageToCloudinary::dispatch($path, $publicId)->onQueue('media');

Pass the path (or a temporary file), not the UploadedFile object — serializing request objects into queue payloads is fragile. Alternatively use Cloudinary's own async => true option and poll/webhook for completion.

Manage lifecycle with a model event or observer. Deleting a record without deleting the asset leaves orphaned storage you pay for:

static::deleted(fn ($post) => Cloudder::delete($post->image_public_id));

Guard against nulls and wrap in try/catch — a Cloudinary outage should not roll back a database delete.

Presigned / unsigned browser uploads. For large files, skip your server entirely: set an unsigned upload preset in the Cloudinary dashboard and post directly from the browser, then send only the returned public_id to your Laravel endpoint. The wrapper is not involved in this flow at all, which is fine — you only need the API credentials for server-side deletion and signed operations. Validate on arrival that the public_id actually exists (Cloudder::show()) before trusting it.

Folder and tag organization. Use folder on upload ('users/avatars', 'posts/2024') and tags for cross-cutting concerns (['featured', 'homepage']). Bulk operations (Cloudder::addTag, removeTag, deleteResourcesByPrefix) are far cheaper than per-asset loops.

Gotchas and Tips

  • This package is archived. Treat every line as legacy. Cloudinary's PHP SDK has since moved namespaces (Cloudinary\Uploader → Cloudinary\Api\Upload\UploadApi), and this wrapper targets the old global-namespace API. Pin cloudinary/cloudinary_php to a compatible major in your composer.json, or a composer update will silently break the facade with "class not found."
  • PHP 8 incompatibility is likely. The pinned SDK era predates PHP 8; expect deprecation notices, dynamic-property warnings, or hard failures on each()/create_function() style code. Verify in a throwaway container before committing to the package.
  • Config caching freezes credentials. php artisan config:cache snapshots env() at build time. Changing CLOUDINARY_* in .env on a cached production box does nothing until you re-cache. Same trap applies to CLOUDINARY_URL, which the underlying SDK reads on its own and can shadow your published config.
  • upload() may return $this, not the result. Verify the signature. If you wrote $result = Cloudder::upload(...) and then $result['public_id'], you may be indexing into an object. Use getResult() / getPublicId() explicitly.
  • Use secure_url, never url. On HTTPS pages the plain url triggers mixed-content blocking, often only visible in the browser console rather than as a server error.
  • Facade state leaks in long-running processes. Under Octane, Horizon, or queue workers, the singleton's uploadedResult/publicId persist between jobs. If a job uploads and a later code path reads getResult() without a fresh upload, you can get the previous job's asset. Reset explicitly or resolve a fresh instance per operation.
  • Transformations in show() must match your account settings. If private_cdn or secure_distribution differs between the SDK config and your account, generated URLs 404 rather than erroring. Confirm the URL Cloudinary's media library produces matches what you render.
  • CDN invalidation isn't instant. Overwriting an asset at the same public_id serves the stale cached version for minutes to hours. Pass invalidate => true, or version the public ID (e.g., append a hash) so the URL changes.
  • delete() succeeds on missing assets. Cloudinary returns "result" => "not found" rather than throwing. Check the response if deletion is idempotency-critical for your cleanup jobs.
  • Uploads from remote URLs can time out. Passing a URL makes Cloudinary fetch it server-side with its own timeout; a slow origin surfaces as your request hanging. Also never pass user-controlled URLs — you're handing Cloudinary an SSRF surface. Whitelist hosts.
  • Upload presets override explicit options. If CLOUDINARY_UPLOAD_PRESET is configured and also passed per-call, the preset wins for fields it defines. Debug "my crop setting is ignored" by checking the preset in the dashboard first.
  • Debugging tip: log the full getResult() array on failure. Cloudinary embeds useful context (error.message, error.http_code) that the wrapper passes straight through without normalization, and PHP's exception message is often just "Bad Request".
  • Migration path: when you have the budget, replace the facade with cloudinary-labs/cloudinary-laravel or the raw SDK behind your service class. Because you isolated calls, this is mechanical — the public ID scheme and stored columns carry over unchanged.
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