jamrul / cloudflare-r2
A lightweight PHP wrapper for Cloudflare R2 object storage built on the AWS S3 SDK. Works with plain PHP and Laravel.
Requires
- php: ^7.4 || ^8.0
- ext-json: *
- aws/aws-sdk-php: ^3.300
Requires (Dev)
- phpunit/phpunit: ^9.6 || ^10.0
- vlucas/phpdotenv: ^5.5
Suggests
- illuminate/support: Required for Laravel integration (service provider / facade).
- vlucas/phpdotenv: Load R2 credentials from a .env file in plain PHP projects.
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-02 19:10:12 UTC
README
A lightweight PHP wrapper for Cloudflare R2 object storage, built on the AWS S3 SDK. Works with plain PHP, any framework, and Laravel (auto-discovered).
Features
- Upload files from
$_FILES, a local path, or raw content - Replace, delete (single or bulk) and list files
- Public URLs and temporary signed URLs for private files
- Automatic unique, sanitized file names
- Real MIME type detection (does not trust the client)
- Optional read / write / delete permission check
- Laravel service provider and publishable config
- No
.envdependency inside the library: pass your config to the constructor
Requirements
- PHP 7.4 or higher
ext-json,ext-fileinfoaws/aws-sdk-php3.337 or higher
Installation
composer require jamrul/cloudflare-r2
Configuration
Create an R2 API token in the Cloudflare dashboard (R2 > Manage R2 API Tokens) with Object Read & Write permission, then add these values to your .env:
R2_ACCOUNT_ID= R2_ACCESS_KEY_ID= R2_SECRET_ACCESS_KEY= R2_BUCKET_NAME= R2_PUBLIC_DOMAIN= R2_DEFAULT_FOLDER= R2_ENABLE_PERMISSION_CHECK=false
| Key | Required | Description |
|---|---|---|
R2_ACCOUNT_ID |
Yes | Your Cloudflare account ID |
R2_ACCESS_KEY_ID |
Yes | R2 API token access key |
R2_SECRET_ACCESS_KEY |
Yes | R2 API token secret |
R2_BUCKET_NAME |
Yes | Bucket name |
R2_PUBLIC_DOMAIN |
No | Public domain of the bucket (custom domain or r2.dev URL). Leave empty for private buckets |
R2_DEFAULT_FOLDER |
No | Folder used when you do not pass one, for example uploads |
R2_ENABLE_PERMISSION_CHECK |
No | Set to true to enable checkPermissions(). Keep it off in production |
Usage with plain PHP
<?php require __DIR__ . '/vendor/autoload.php'; use Jamrul\Cloudflare\R2Service; $r2 = new R2Service( 'your-account-id', 'your-access-key-id', 'your-secret-access-key', 'your-bucket-name', 'https://cdn.example.com', // public domain (optional) 'uploads' // default folder (optional) ); // Upload from a form if (!empty($_FILES['file'])) { $result = $r2->uploadFile($_FILES['file']); echo $result['url']; }
To load credentials from a .env file, install vlucas/phpdotenv and read the values yourself:
Dotenv\Dotenv::createImmutable(__DIR__)->safeLoad(); $r2 = new R2Service( $_ENV['R2_ACCOUNT_ID'] ?? null, $_ENV['R2_ACCESS_KEY_ID'] ?? null, $_ENV['R2_SECRET_ACCESS_KEY'] ?? null, $_ENV['R2_BUCKET_NAME'] ?? null, $_ENV['R2_PUBLIC_DOMAIN'] ?? '', $_ENV['R2_DEFAULT_FOLDER'] ?? '', filter_var($_ENV['R2_ENABLE_PERMISSION_CHECK'] ?? false, FILTER_VALIDATE_BOOLEAN) );
Usage with Laravel
The service provider is registered automatically. Add the values above to your .env and inject the service:
use Illuminate\Http\Request; use Jamrul\Cloudflare\R2Service; public function store(Request $request, R2Service $r2) { $file = $request->file('image'); $result = $r2->uploadFromPath( $file->getRealPath(), 'avatars', $file->getClientOriginalName() ); return $result['url']; }
You can also resolve it with app('r2').
To publish the config file to config/r2.php:
php artisan vendor:publish --tag=r2-config
Available methods
| Method | Description |
|---|---|
uploadFile(array $file, ?string $folder = null) |
Upload a $_FILES entry |
uploadFromPath(string $path, ?string $folder = null, ?string $customName = null) |
Upload a file from disk |
uploadContent(string $content, string $fileName, ?string $folder = null, string $contentType = 'application/octet-stream') |
Upload raw content (generated PDF, JSON, and so on) |
updateFile(string $oldKey, array $newFile, ?string $folder = null) |
Upload a new file, then delete the old one |
deleteFile(string $key) |
Delete one file |
deleteFiles(array $keys) |
Delete many files (batched by 1000) |
fileExists(string $key) |
Check whether a file exists |
getUrl(string $key) |
Public URL (empty string if no public domain is set) |
temporaryUrl(string $key, int $minutes = 60) |
Signed URL for private files |
listFiles(?string $folder = null) |
List files in a folder (handles pagination) |
getImages(?string $folder = null) |
Alias of listFiles() |
checkPermissions() |
Test read, write and delete access |
Upload methods return:
[
'key' => 'uploads/1735700000_a1b2c3_photo.jpg',
'file_name' => 'photo.jpg',
'url' => 'https://cdn.example.com/uploads/1735700000_a1b2c3_photo.jpg',
'size' => 20480,
'type' => 'image/jpeg',
]
Store the key in your database. You need it to delete, replace or sign the file later.
Error handling
Failures throw \RuntimeException (the original AWS exception is available through getPrevious()). Missing configuration throws \InvalidArgumentException.
try { $r2->uploadFile($_FILES['file']); } catch (\RuntimeException $e) { echo $e->getMessage(); }
Demo page
examples/index.php is a ready-made test page for upload, replace, delete and list. Copy it next to your .env, run composer require vlucas/phpdotenv, and open it in the browser.
The demo has no authentication. Use it only for local testing and never deploy it publicly.
Security notes
- Never commit your
.envfile or API credentials. - Validate file type and size in your own application before uploading.
- Use
temporaryUrl()instead of a public domain for private files. - Keep
R2_ENABLE_PERMISSION_CHECKoff in production. The check writes and deletes a test file every time it runs.
License
Released under the MIT License.