onurozdogan/cloudflare-image-api

Cloudflare Image API PHP Library

Maintainers

Package info

github.com/onurzdgn/cloudflare-image-api

pkg:composer/onurozdogan/cloudflare-image-api

Transparency log

Statistics

Installs: 265

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-08-03 08:11 UTC

README

A small Laravel package for uploading, replacing, and deleting images in Cloudflare Images.

The package supports Laravel 9 through 13. Laravel 13.20's first-party Image API is supported as an optional preprocessing layer; Laravel 9–12 applications can continue uploading regular Laravel or Livewire uploaded files.

Requirements

  • PHP 8.0 or later
  • Laravel 9, 10, 11, 12, or 13
  • Guzzle 7
  • A Cloudflare account with an API token that has Images Write permission
  • Optional: Livewire 3 for Livewire temporary uploads
  • Optional: intervention/image:^4.0 for Laravel 13.20's Image API

The PHP version required by your Laravel version still applies. For example, Laravel 13 may require a newer PHP version even though this package's own code supports PHP 8.0.

Security note: Laravel 9–11 have reached end of security support. They are retained here for backwards compatibility, but new and internet-facing applications should use a currently supported Laravel release. Current Composer security policies may block fresh installation of vulnerable EOL framework versions.

Installation

composer require onurozdogan/cloudflare-image-api

Laravel discovers the service provider and facade automatically.

Configuration

Add your Cloudflare API token and account ID to .env:

CLOUDFLARE_API_TOKEN=your-api-token
CLOUDFLARE_ACCOUNT_ID=your-account-id

CLOUDFLARE_API_KEY remains supported for backwards compatibility, but new applications should use CLOUDFLARE_API_TOKEN.

You may publish the configuration file:

php artisan vendor:publish --tag=cloudflareimageapi

Uploading an Uploaded File

Laravel's UploadedFile and Livewire 3's TemporaryUploadedFile are accepted. Livewire is optional because its temporary upload class extends Laravel's UploadedFile.

use CloudflareImageApi;

$request->validate([
    'image' => ['required', 'image'],
]);

$response = CloudflareImageApi::upload(
    $request->file('image'),
    'profile-photo.jpg',
);

if ($response->getStatusCode() === 200) {
    $imageId = $response->getData()->photoId;
}

A local file path is also accepted:

$response = CloudflareImageApi::upload(
    storage_path('app/images/photo.jpg'),
    'photo.jpg',
);

Laravel 13.20 Image Support

Laravel 13.20 can resize, crop, orient, and encode an image before this package uploads the processed bytes to Cloudflare. Install Laravel's optional image engine first:

composer require intervention/image:^4.0

You may use the explicit Laravel adapter:

$image = $request->image('photo')
    ->orient()
    ->scale(2048, 2048)
    ->optimize(format: 'webp', quality: 80);

$response = CloudflareImageApi::uploadLaravelImage(
    $image,
    'photo.webp',
);

Or pass the processed bytes directly:

$response = CloudflareImageApi::uploadBytes(
    $image->toBytes(),
    'photo.webp',
    $image->mimeType(),
);

uploadBytes() is framework-independent and may also be used with Intervention Image, Imagick, or any other image processor. Calling uploadLaravelImage() on Laravel versions older than 13.20 returns a 422 response; it does not prevent the package from being installed or used on those versions.

For large images, perform preprocessing and upload in a queued job to avoid high CPU and memory usage during an HTTP request.

Uploading a Stream

PHP resources and PSR-7 streams are supported:

$stream = fopen(storage_path('app/images/photo.jpg'), 'rb');

try {
    $response = CloudflareImageApi::uploadStream(
        $stream,
        'photo.jpg',
        'image/jpeg',
    );
} finally {
    fclose($stream);
}

Replacing an Image Safely

update() uploads the new image first. The old image is deleted only after the new upload succeeds, so a failed upload does not remove the existing image.

$response = CloudflareImageApi::update(
    $model->cloudflare_image_id,
    $request->file('image'),
    'profile-photo.jpg',
);

$data = $response->getData();

if ($response->getStatusCode() === 200) {
    $model->cloudflare_image_id = $data->photoId;
    $model->save();
}

Processed bytes can be used with updateBytes():

$response = CloudflareImageApi::updateBytes(
    $model->cloudflare_image_id,
    $image->toBytes(),
    'profile-photo.webp',
    $image->mimeType(),
);

If the new image uploads but the old image cannot be deleted, the response still has status 200 and includes the new photoId, a warning, and oldPhotoId. Persist the new ID and schedule cleanup of the old ID.

Direct Browser or Mobile Uploads

Use a one-time Direct Creator Upload URL when the browser or mobile application should upload to Cloudflare without sending the file through your Laravel server:

$response = CloudflareImageApi::createDirectUploadUrl([
    'requireSignedURLs' => true,
    'metadata' => ['user_id' => (string) $request->user()->id],
]);

return response()->json($response->getData());

The successful response contains:

{
    "imageId": "cloudflare-draft-image-id",
    "uploadUrl": "https://upload.imagedelivery.net/...",
    "tmpUrl": "https://upload.imagedelivery.net/..."
}

tmpUrl and the deprecated createTmpUrl() method are retained for backwards compatibility.

Deleting an Image

$response = CloudflareImageApi::delete($model->cloudflare_image_id);

if ($response->getStatusCode() !== 200) {
    $message = $response->getData()->error;
}

Verifying the API Token

Normal operations rely on Cloudflare's own authentication response and do not make an extra verification request. You can explicitly verify the configured token when diagnosing configuration:

$response = CloudflareImageApi::controlApiToken();

Displaying an Image

Use the image ID returned by Cloudflare with one of your configured variants:

<img
    src="https://imagedelivery.net/your-account-hash/{{ $model->cloudflare_image_id }}/public"
    alt="{{ $model->title }}"
    loading="lazy"
>

Error Handling

Methods return a Laravel JSON response. Successful operations return status 200. Invalid local input returns 422; Cloudflare authentication, validation, and rate-limit statuses are preserved when possible; unexpected upstream errors return 502.

if ($response->getStatusCode() !== 200) {
    $error = $response->getData()->error;
}

Cloudflare's safe API error message may be returned, but raw HTTP exception details and credentials are not exposed.

Testing

composer test

Internal Architecture

The public API remains in CloudflareImageApi. Internally, responsibilities are kept separate:

  • CloudflareImageApi orchestrates upload, replacement, deletion, and JSON responses.
  • CloudflareImagesClient owns Cloudflare endpoints, authentication, and API errors.
  • ImagePayload normalizes files, bytes, and streams and safely manages owned resources.
  • CloudflareApiException carries safe upstream errors and HTTP status codes.

This separation keeps transport and input-handling details out of the public service while preserving the existing facade API.

Security

If you discover a security issue, please use the contact form at onurozdogan.com.

License

The MIT License. See LICENSE.