onurozdogan / cloudflare-image-api
Cloudflare Image API PHP Library
Requires
- php: ^8.0
- guzzlehttp/guzzle: ^7.0
- illuminate/http: ^9.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0
- illuminate/support: ^9.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0
Requires (Dev)
- orchestra/testbench: ^7.0 || ^8.0 || ^9.0 || ^10.0 || ^11.0
- phpunit/phpunit: ^9.5 || ^10.5 || ^11.0 || ^12.0
Suggests
- intervention/image: Required by Laravel 13.20+ when using its first-party Image API.
- livewire/livewire: Allows Livewire temporary uploaded files to be passed to upload().
This package is auto-updated.
Last update: 2026-08-03 08:19:43 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 Writepermission - Optional: Livewire 3 for Livewire temporary uploads
- Optional:
intervention/image:^4.0for 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:
CloudflareImageApiorchestrates upload, replacement, deletion, and JSON responses.CloudflareImagesClientowns Cloudflare endpoints, authentication, and API errors.ImagePayloadnormalizes files, bytes, and streams and safely manages owned resources.CloudflareApiExceptioncarries 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.