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

cloudinary/cloudinary_php

Cloudinary PHP SDK lets PHP applications upload, transform, optimize, and manage images and videos in Cloudinary. Install via Composer to generate image/video tags, secure URLs, and handle asset management. Version 3.x supports PHP 8.0–8.4.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation

    composer require cloudinary/cloudinary_php
    

    Register the service provider in config/app.php (Laravel 5.5+ auto-discovers).

  2. Configuration Add credentials to .env:

    CLOUDINARY_CLOUD_NAME=your_cloud_name
    CLOUDINARY_API_KEY=your_api_key
    CLOUDINARY_API_SECRET=your_api_secret
    

    Publish the config file:

    php artisan vendor:publish --provider="Cloudinary\CloudinaryLaravel\CloudinaryServiceProvider"
    

    Edit config/cloudinary.php for default settings (e.g., folder structure, transformations).

  3. First Use Case Upload an image from a request:

    use Cloudinary\Cloudinary;
    use Cloudinary\Configuration\Configuration;
    
    $cloudinary = new Cloudinary(new Configuration([
        'cloud' => [
            'cloud_name' => env('CLOUDINARY_CLOUD_NAME'),
            'api_key'    => env('CLOUDINARY_API_KEY'),
            'api_secret' => env('CLOUDINARY_API_SECRET'),
        ],
    ]));
    
    $result = $cloudinary->uploadApi()->upload(
        fopen($_FILES['image']['tmp_name'], 'r'),
        ['folder' => 'user_uploads']
    );
    
  4. What's New in 3.3.0 — Custom PSR-18 HTTP Client The SDK now accepts any PSR-18 ClientInterface implementation, so you can control transport behaviour (timeouts, retries, proxies, logging, tracing) or swap in a mock client for tests. See Implementation Patterns → Custom HTTP Client below.


Implementation Patterns

Common Workflows

1. Uploading Files

  • From Requests
    $result = $cloudinary->uploadApi()->upload(
        fopen($_FILES['file']['tmp_name'], 'r'),
        ['folder' => 'uploads/' . auth()->id()]
    );
    
  • From URLs
    $result = $cloudinary->uploadApi()->upload(
        'https://example.com/image.jpg',
        ['folder' => 'remote_uploads']
    );
    

2. Custom HTTP Client (PSR-18) (new in 3.3.0)

Inject a PSR-18 client into the Cloudinary configuration — useful for retries, logging, timeouts, proxies and testing:

use Cloudinary\Cloudinary;
use Cloudinary\Configuration\Configuration;
use GuzzleHttp\Client;

$config = new Configuration(config('cloudinary'));
$config->httpClient = new Client([
    'timeout'         => 30,
    'connect_timeout' => 5,
]);

$cloudinary = new Cloudinary($config);

Bind it once in a service provider so the whole app shares the configured client:

// app/Providers/AppServiceProvider.php
public function register(): void
{
    $this->app->singleton(Cloudinary::class, function () {
        $config = new Configuration(config('cloudinary'));
        $config->httpClient = new \GuzzleHttp\Client([
            'timeout' => 30,
            'handler' => \GuzzleHttp\HandlerStack::create(), // add middleware here
        ]);

        return new Cloudinary($config);
    });
}

Guzzle and symfony/http-client (Psr18Client) both satisfy PSR-18. Laravel's own Http facade does not — wrap Guzzle or Symfony instead. Because the client is now a dependency, you can attach middleware for request IDs, structured logging, or metrics without touching SDK internals.

3. Generating URLs

  • Dynamic Transformations
    $url = $cloudinary->url()->transformation([
        'width' => 500,
        'height' => 300,
        'crop' => 'limit',
        'gravity' => 'face'
    ])->generate('sample.jpg');
    
  • Signed URLs (for private assets)
    $url = $cloudinary->url()->transformation([
        'effect' => 'sepia'
    ])->generate('private.jpg', ['expires' => time() + 3600]);
    

4. Deleting Assets

$cloudinary->deleteResourcesByTag('user_' . auth()->id());
// OR delete by public ID
$cloudinary->deleteResources(['public_id' => 'sample.jpg']);

5. Batch Operations

$publicIds = ['image1.jpg', 'image2.jpg'];
$cloudinary->deleteResources($publicIds);

6. Integration with Laravel Storage

Create a custom Cloudinary adapter for Storage::disk():

// config/filesystems.php
'cloudinary' => [
    'driver' => 'cloudinary',
    'cloudinary' => [
        'cloud_name' => env('CLOUDINARY_CLOUD_NAME'),
        'api_key'    => env('CLOUDINARY_API_KEY'),
        'api_secret' => env('CLOUDINARY_API_SECRET'),
    ],
],

Then use it like any other disk:

Storage::disk('cloudinary')->put('folder/file.jpg', fopen('local.jpg', 'r'));

7. Laravel Eloquent Relationships

Use accessors/mutators in models:

class Product extends Model {
    public function getImageUrlAttribute() {
        return $this->image_id
            ? $this->cloudinary()->url()->generate($this->image_id)
            : null;
    }

    public function setImageUrlAttribute($url) {
        $result = $this->cloudinary()->uploadApi()->upload($url);
        $this->image_id = $result['public_id'];
    }
}

Gotchas and Tips

Pitfalls and Debugging

  1. API Key/Secret Mismatch

    • Symptom: Authentication failed errors.
    • Fix: Double-check .env and ensure no trailing spaces in credentials.
  2. Folder Permissions

    • Symptom: Uploads fail silently or appear in the root folder.
    • Fix: Explicitly set folders in upload options:
      ['folder' => 'user_' . auth()->id() . '/uploads']
      
  3. Transformation Errors

    • Symptom: Invalid transformation or broken images.
    • Fix: Validate transformations using Cloudinary’s transformation reference.
    • Debug: Use the generate() method to test URLs before rendering.
  4. Rate Limiting

    • Symptom: 429 Too Many Requests.
    • Fix: Implement exponential backoff or upgrade your plan. With 3.3.0 you can put the backoff inside a PSR-18 client/middleware instead of scattering try/catch blocks around your code.
  5. Private Assets

    • Gotcha: Signed URLs expire. Cache them or regenerate on demand.
    • Tip: Use middleware to validate signed URLs:
      Route::get('/private/{id}', function ($id) {
          $url = $this->cloudinary->url()->generate($id, ['expires' => time() + 300]);
          return redirect()->to($url);
      });
      
  6. Custom PSR-18 Client Pitfalls (3.3.0)

    • Must be PSR-18: the object needs to implement Psr\Http\Client\ClientInterface. Illuminate\Support\Facades\Http does not — use GuzzleHttp\Client or Symfony\Component\HttpClient\Psr18Client.
    • Exceptions must be PSR-18 compliant: if your wrapper swallows ClientExceptionInterface, the SDK can't surface API errors and you'll get confusing "empty response" failures. Always rethrow or wrap as a PSR-18 exception.
    • Don't retry non-idempotent uploads blindly: retry middleware on a POST /upload can create duplicate assets on a timeout-then-success. Restrict retries to reads (URL/admin calls) or use Cloudinary upload_preset/public_id idempotency by supplying a deterministic public_id.
    • Timeouts for large files: a default Guzzle timeout of 5s will kill big uploads. Set timeout in the tens of seconds (or none) and a modest connect_timeout.
    • Don't disable SSL verification to "fix" proxy issues — configure verify with your CA bundle or proxy instead.
    • Missing package: guzzlehttp/guzzle isn't pulled in by the SDK for this feature; add it explicitly if you use it.

Configuration Quirks

  1. Default Folder

    • If not specified, uploads go to the root. Set a default in config/cloudinary.php:
      'default_folder' => 'app_uploads',
      
  2. Transformation Caching

    • Enable caching for performance:
      $url = $cloudinary->url()->transformation([
          'fetch_format' => 'auto',
          'quality' => 'auto'
      ])->generate('image.jpg', ['c_name' => 'my_cache']);
      
  3. Async Uploads

    • For large files, use the upload() method with async:
      $result = $cloudinary->uploadApi()->upload($file, ['async' => true]);
      
  4. Where to Configure the HTTP Client

    • The client is set on the Configuration object, not in config/cloudinary.php's credential array (it can't be serialised). Keep it in a service provider or a small factory class so config:cache stays safe.

Extension Points

  1. Custom Upload Presets

    • Create reusable presets for common transformations:
      $cloudinary->uploadApi()->upload($file, [
          'upload_preset' => 'my_preset_name'
      ]);
      
    • Define presets in Cloudinary’s dashboard under Settings > Upload.
  2. Webhooks

    • Use Cloudinary’s notifications to trigger Laravel events:
      // Example: Listen for upload notifications
      $cloudinary->uploadApi()->upload($file, [
          'notification_url' => route('cloudinary.webhook')
      ]);
      
  3. Middleware for Transformations

    • Create middleware to apply default transformations to all image URLs:
      public function handle($request, Closure $next) {
          $response = $next($request);
          $response->setContent(
              str_replace(
                  'sample.jpg',
                  $this->cloudinary->url()->transformation(['width' => 800])->generate('sample.jpg'),
                  $response->getContent()
              )
          );
          return $response;
      }
      
  4. HTTP Client Middleware as an Extension Point (3.3.0)

    • Log every Cloudinary call with timing and status:
      $stack = HandlerStack::create();
      $stack->push(Middleware::tap(
          fn ($req) => logger()->debug('cloudinary.request', ['uri' => (string) $req->getUri()]),
          fn ($req, $res) => logger()->debug('cloudinary.response', ['status' => $res->getStatusCode()])
      ));
      $config->httpClient = new Client(['handler' => $stack]);
      
    • Inject headers for tracing (X-Request-Id), or route through an egress proxy in locked-down environments.
  5. Fallback for Local Storage

    • Implement a fallback if Cloudinary is down:
      try {
          $url = $cloudinary->url()->generate('image.jpg');
      } catch (\Exception $e) {
          $url = Storage::disk('local')->url('fallback/image.jpg');
      }
      
  6. Testing

    • Prefer a PSR-18 mock client over mocking the Cloudinary class (closer to the real code path, available since 3.3.0):
      use Cloudinary\Cloudinary;
      use Cloudinary\Configuration\Configuration;
      use Http\Mock\Client as MockClient;
      
      $mock = new MockClient();          // php-http/mock-client
      $mock->addResponse(new \GuzzleHttp\Psr7\Response(200, [], json_encode([
          'public_id' => 'sample', 'secure_url' => 'https://res.cloudinary.com/demo/sample.jpg',
      ])));
      
      $config = new Configuration(config('cloudinary'));
      $config->httpClient = $mock;
      
      $this->app->instance(Cloudinary::class, new Cloudinary($config));
      
    • You can still mock the SDK itself for pure unit tests:
      $cloudinary = Mockery::mock(Cloudinary::class);
      $cloudinary->shouldReceive('url')->andReturnSelf();
      $cloudinary->shouldReceive('generate')->andReturn('http://test.url/image.jpg');
      
    • Use a .env.testing with placeholder credentials so URL generation (which is signing-only, no HTTP) works offline.
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.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log