PHPackages                             nks-hub/nette-cloudflare-r2 - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [File &amp; Storage](/categories/file-storage)
4. /
5. nks-hub/nette-cloudflare-r2

ActiveLibrary[File &amp; Storage](/categories/file-storage)

nks-hub/nette-cloudflare-r2
===========================

Cloudflare R2 storage integration for Nette Framework with full S3-compatible API support

v1.1.0(1mo ago)05MITPHPPHP &gt;=8.1

Since Jan 23Pushed 1mo agoCompare

[ Source](https://github.com/nks-hub/nette-cloudflare-r2)[ Packagist](https://packagist.org/packages/nks-hub/nette-cloudflare-r2)[ Docs](https://github.com/nks-hub/nette-cloudflare-r2)[ RSS](/packages/nks-hub-nette-cloudflare-r2/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependencies (16)Versions (3)Used By (0)

Nette Cloudflare R2
===================

[](#nette-cloudflare-r2)

[![Latest Stable Version](https://camo.githubusercontent.com/fab4b362aeffe09a6031e06e6809227ac9896b94be39513f3407a587f36b9f99/68747470733a2f2f706f7365722e707567782e6f72672f6e6b732d6875622f6e657474652d636c6f7564666c6172652d72322f76)](https://packagist.org/packages/nks-hub/nette-cloudflare-r2)[![Total Downloads](https://camo.githubusercontent.com/0162eda51ec2aab86507e9dbe8fdce81eb3d20b249f7c490aec0dcbce0a7d3d4/68747470733a2f2f706f7365722e707567782e6f72672f6e6b732d6875622f6e657474652d636c6f7564666c6172652d72322f646f776e6c6f616473)](https://packagist.org/packages/nks-hub/nette-cloudflare-r2)[![PHP Version](https://camo.githubusercontent.com/291fb48c27888cb58d3daa496237532ea2d0bdd51a933f0a7a4262e9d87b7304/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d253345253344372e342d3838393242462e737667)](https://php.net/)[![Nette Version](https://camo.githubusercontent.com/445b5b9d7b82252a8434799d6132774b91be19bdd63bbedc05d2454f9845ae1a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6e657474652d253545332e302d626c75652e737667)](https://nette.org/)[![License](https://camo.githubusercontent.com/8bb50fd2278f18fc326bf71f6e88ca8f884f72f179d3e555e20ed30157190d0d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e2e737667)](LICENSE)

Cloudflare R2 storage integration for Nette Framework with full S3-compatible API support.

Features
--------

[](#features)

- 🚀 **Full S3-compatible API** - Upload, download, delete, copy, list objects
- 📦 **Multipart uploads** - Automatic chunked upload for large files
- 🔗 **Presigned URLs** - Generate temporary access URLs (up to 7 days)
- 🎯 **Nette integration** - DI extension, FileUpload support, Tracy panel
- 💾 **Storage classes** - Standard and Infrequent Access support
- 🔄 **Lifecycle rules** - Automatic expiration and transitions
- 🌐 **Cloudflare API** - Custom domains, event notifications, metrics
- 🛡️ **Type-safe** - Full PHP 7.4+ / 8.x support with strict types

Requirements
------------

[](#requirements)

- PHP 7.4 or higher
- Nette Framework 3.x
- Cloudflare R2 account with API credentials

Installation
------------

[](#installation)

```
composer require nks-hub/nette-cloudflare-r2
```

Configuration
-------------

[](#configuration)

### Basic Setup

[](#basic-setup)

Register the extension in your `config.neon`:

```
extensions:
    r2: NksHub\NetteCloudflareR2\DI\CloudflareR2Extension

r2:
    accountId: 'your-account-id'
    accessKeyId: 'your-access-key-id'
    secretAccessKey: 'your-secret-access-key'
    defaultBucket: 'my-bucket'
```

### Full Configuration

[](#full-configuration)

```
r2:
    # Required credentials
    accountId: %env.R2_ACCOUNT_ID%
    accessKeyId: %env.R2_ACCESS_KEY_ID%
    secretAccessKey: %env.R2_SECRET_ACCESS_KEY%
    defaultBucket: 'my-bucket'

    # Optional: Custom domain for public URLs
    publicUrl: 'https://cdn.example.com'

    # Optional: Jurisdictional restriction (eu, fedramp)
    jurisdiction: eu

    # Upload defaults
    upload:
        storageClass: STANDARD          # STANDARD or STANDARD_IA
        cacheControl: 'max-age=31536000'
        chunkSize: 8388608              # 8MB
        autoMultipart: true

    # Named buckets (optional)
    buckets:
        images:
            name: 'my-images-bucket'
            publicUrl: 'https://images.example.com'
        backups:
            name: 'my-backups-bucket'
            storageClass: STANDARD_IA

    # Debug options
    tracy: %debugMode%
    logging: false
```

Usage
-----

[](#usage)

### Basic Operations

[](#basic-operations)

```
use NksHub\NetteCloudflareR2\Client\R2Client;

class MyPresenter extends Nette\Application\UI\Presenter
{
    public function __construct(
        private R2Client $r2
    ) {}

    public function actionUpload(): void
    {
        // Upload string content
        $url = $this->r2->upload('path/to/file.txt', 'Hello World!');

        // Upload from local file
        $url = $this->r2->uploadFromPath('images/photo.jpg', '/local/path/photo.jpg');

        // Download content
        $content = $this->r2->get('path/to/file.txt');

        // Download to local file
        $this->r2->download('images/photo.jpg', '/local/destination.jpg');

        // Delete
        $this->r2->delete('path/to/file.txt');

        // Check existence
        if ($this->r2->exists('path/to/file.txt')) {
            // File exists
        }

        // Get metadata
        $metadata = $this->r2->getMetadata('path/to/file.txt');
        echo $metadata->getSize();
        echo $metadata->getContentType();
        echo $metadata->getFormattedSize(); // "1.5 MB"
    }
}
```

### File Upload Integration

[](#file-upload-integration)

```
use Nette\Http\FileUpload;
use NksHub\NetteCloudflareR2\Client\R2Client;

class GalleryPresenter extends Nette\Application\UI\Presenter
{
    public function __construct(
        private R2Client $r2
    ) {}

    public function handleUploadPhoto(): void
    {
        /** @var FileUpload $file */
        $file = $this->getHttpRequest()->getFile('photo');

        if ($file && $file->isOk() && $file->isImage()) {
            // Upload with auto-generated filename
            $url = $this->r2->uploadFile($file, 'gallery/' . $this->user->id);

            // Save URL to database
            $this->galleryRepository->insert([
                'user_id' => $this->user->id,
                'url' => $url,
            ]);

            $this->flashMessage('Photo uploaded successfully');
        }

        $this->redirect('this');
    }
}
```

### Upload Options

[](#upload-options)

```
use NksHub\NetteCloudflareR2\Storage\UploadOptions;
use NksHub\NetteCloudflareR2\Storage\StorageClass;

// Create options
$options = UploadOptions::create()
    ->withContentType('image/jpeg')
    ->withCacheControl('max-age=86400')
    ->withStorageClass(StorageClass::INFREQUENT_ACCESS)
    ->withMetadata(['author' => 'John Doe']);

$url = $r2->upload('photo.jpg', $content, $options);
```

### Presigned URLs

[](#presigned-urls)

```
// Temporary download URL (1 hour)
$presignedUrl = $r2->getPresignedUrl('private/document.pdf', 3600);
echo $presignedUrl->getUrl();
echo $presignedUrl->getExpiresAt()->format('Y-m-d H:i:s');

// Temporary upload URL
$uploadUrl = $r2->getPresignedUploadUrl('uploads/new-file.jpg', 600);

// Check if expired
if ($presignedUrl->isExpired()) {
    // Generate new URL
}
```

### Listing Objects

[](#listing-objects)

```
// List with pagination
$result = $r2->list('images/', maxKeys: 100);
foreach ($result['objects'] as $object) {
    echo $object->getKey();
    echo $object->getSize();
}

if ($result['isTruncated']) {
    // Get next page
    $nextResult = $r2->list('images/', 100, $result['nextToken']);
}

// List all (auto-pagination)
foreach ($r2->listAll('images/') as $object) {
    echo $object->getKey();
}

// Count objects
$count = $r2->count('images/');
```

### Multiple Buckets

[](#multiple-buckets)

```
use NksHub\NetteCloudflareR2\Client\R2ClientFactory;

class MyService
{
    public function __construct(
        private R2Client $r2,           // Default bucket
        private R2ClientFactory $factory
    ) {}

    public function uploadToImages(string $content): string
    {
        return $this->factory->create('images')->upload('file.jpg', $content);
    }

    public function uploadToBackups(string $content): string
    {
        return $this->factory->create('backups')->upload('backup.zip', $content);
    }
}
```

### Copy and Metadata

[](#copy-and-metadata)

```
// Copy object
$newUrl = $r2->copy('original.jpg', 'copy.jpg');

// Update metadata
$r2->setMetadata('file.jpg', ['version' => '2']);

// Change storage class
$r2->changeStorageClass('archive.zip', StorageClass::INFREQUENT_ACCESS);
```

### Multipart Upload (Large Files)

[](#multipart-upload-large-files)

```
// Automatic multipart for large files (>100MB by default)
$url = $r2->upload('large-file.zip', $largeContent);

// Manual multipart upload
$multipart = $r2->multipart();
$uploadId = $multipart->create('huge-file.zip');

$parts = [];
$partNumber = 1;
foreach ($chunks as $chunk) {
    $parts[] = $multipart->uploadPart('huge-file.zip', $uploadId, $partNumber++, $chunk);
}

$multipart->complete('huge-file.zip', $uploadId, $parts);

// With progress tracking
use NksHub\NetteCloudflareR2\Upload\ChunkedUploader;

$uploader = new ChunkedUploader($r2);
$uploader->onProgress(function (int $uploaded, int $total) {
    echo round($uploaded / $total * 100) . '%';
});
$url = $uploader->upload('huge-file.zip', '/local/path/huge-file.zip');
```

### Lifecycle Rules

[](#lifecycle-rules)

```
// Add expiration rule (delete after 90 days)
$r2->lifecycle()->addExpirationRule('logs/', days: 90);

// Transition to Infrequent Access after 30 days
$r2->lifecycle()->addTransitionRule(
    'archive/',
    days: 30,
    targetClass: StorageClass::INFREQUENT_ACCESS
);

// Abort incomplete multipart uploads after 7 days
$r2->lifecycle()->addAbortIncompleteMultipartRule(days: 7);

// Get current rules
$rules = $r2->lifecycle()->get();

// Remove a rule
$r2->lifecycle()->removeRule('expire-logs-90d');
```

### Bucket Operations

[](#bucket-operations)

```
// List buckets
$buckets = $r2->buckets()->list();

// Create bucket
$r2->buckets()->create('new-bucket', locationHint: 'weur');

// Delete bucket
$r2->buckets()->delete('old-bucket');

// CORS configuration
$r2->buckets()->setCors([
    [
        'AllowedOrigins' => ['https://example.com'],
        'AllowedMethods' => ['GET', 'PUT', 'POST'],
        'AllowedHeaders' => ['*'],
        'MaxAgeSeconds' => 3600,
    ],
]);
```

### Cloudflare API (Extended Features)

[](#cloudflare-api-extended-features)

```
use NksHub\NetteCloudflareR2\Api\CloudflareApi;

$api = new CloudflareApi($accountId, $apiToken);

// Custom domains
$api->attachCustomDomain('my-bucket', 'cdn.example.com');
$api->listCustomDomains('my-bucket');

// Enable r2.dev public access
$api->setManagedDomain('my-bucket', enabled: true);

// Event notifications
$api->createEventNotification(
    'my-bucket',
    'queue-id',
    ['object-create', 'object-delete'],
    prefix: 'uploads/'
);

// Get metrics
$metrics = $api->getMetrics();

// Temporary credentials
$creds = $api->createTempCredentials(
    'my-bucket',
    permission: 'object-read-only',
    ttlSeconds: 3600
);
```

### Stream Operations

[](#stream-operations)

```
use NksHub\NetteCloudflareR2\Upload\StreamUploader;
use NksHub\NetteCloudflareR2\Download\StreamDownloader;

// Upload from URL
$streamUploader = new StreamUploader($r2);
$url = $streamUploader->uploadFromUrl('image.jpg', 'https://example.com/image.jpg');

// Upload base64
$url = $streamUploader->uploadBase64('image.png', $base64Data);

// Stream download
$streamDownloader = new StreamDownloader($r2);
$streamDownloader->streamToOutput('file.pdf', $this->getHttpResponse(), 'document.pdf');

// Create FileResponse
$response = $streamDownloader->createFileResponse('file.pdf', 'download.pdf');
$this->sendResponse($response);
```

### Tracy Debugger Panel

[](#tracy-debugger-panel)

When `tracy: true` is enabled, you'll see R2 statistics in the Tracy bar:

- Total operations count
- Uploads/downloads/deletes
- Bytes transferred
- Error count
- Configuration details

Storage Classes
---------------

[](#storage-classes)

ClassUse CaseRetrieval Fee`STANDARD`Frequently accessed dataNone`STANDARD_IA`Infrequently accessed dataYes```
use NksHub\NetteCloudflareR2\Storage\StorageClass;

$options = UploadOptions::create()
    ->withStorageClass(StorageClass::INFREQUENT_ACCESS);
```

Error Handling
--------------

[](#error-handling)

```
use NksHub\NetteCloudflareR2\Exception\R2Exception;
use NksHub\NetteCloudflareR2\Exception\ObjectException;
use NksHub\NetteCloudflareR2\Exception\AuthenticationException;
use NksHub\NetteCloudflareR2\Exception\RateLimitException;

try {
    $r2->get('non-existent-file.txt');
} catch (ObjectException $e) {
    // Object not found
    echo $e->getR2ErrorCode(); // 10007
} catch (AuthenticationException $e) {
    // Invalid credentials
} catch (RateLimitException $e) {
    // Rate limited (1 write/second per key)
    sleep($e->getRetryAfter());
} catch (R2Exception $e) {
    // Other R2 errors
}
```

R2 Pricing Benefits
-------------------

[](#r2-pricing-benefits)

Cloudflare R2 offers significant cost savings:

FeatureR2AWS S3Storage$0.015/GB$0.023/GBEgress**$0 (FREE!)**$0.09/GBClass A ops$4.50/million$5.00/millionClass B ops$0.36/million$0.40/million**Free tier**: 10 GB storage, 1M Class A ops, 10M Class B ops per month.

Testing
-------

[](#testing)

```
./vendor/bin/tester tests
```

Contributing
------------

[](#contributing)

Contributions are welcome! For major changes, please open an issue first.

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'feat: description'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

Support
-------

[](#support)

- 📧 **Email:**
- 🐛 **Bug reports:** [GitHub Issues](https://github.com/nks-hub/nette-cloudflare-r2/issues)
- 📖 **Cloudflare R2:** [cloudflare.com/products/r2](https://www.cloudflare.com/products/r2/)

License
-------

[](#license)

MIT License — see [LICENSE](LICENSE) for details.

---

 Made with ❤️ by [NKS Hub](https://github.com/nks-hub)

###  Health Score

38

—

LowBetter than 85% of packages

Maintenance90

Actively maintained with recent releases

Popularity4

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity44

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~57 days

Total

2

Last Release

51d ago

PHP version history (2 changes)v1.0.0PHP &gt;=7.4

v1.1.0PHP &gt;=8.1

### Community

Maintainers

![](https://www.gravatar.com/avatar/01e4841749ee2b4cf7211d784b093bf67523ed6489c6931401211b130df13884?d=identicon)[lukyrys](/maintainers/lukyrys)

---

Top Contributors

[![lukyrys](https://avatars.githubusercontent.com/u/2318346?v=4)](https://github.com/lukyrys "lukyrys (24 commits)")

---

Tags

cdncloudflarecloudflare-r2composerfile-uploadmultipart-uploadnette-extensionnette-frameworkobject-storagephppresigned-urls3tracys3cloudnettestorageuploadcloudflarecdnr2

###  Code Quality

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/nks-hub-nette-cloudflare-r2/health.svg)

```
[![Health](https://phpackages.com/badges/nks-hub-nette-cloudflare-r2/health.svg)](https://phpackages.com/packages/nks-hub-nette-cloudflare-r2)
```

###  Alternatives

[contributte/image-storage

Image storage for Nette framework

28749.3k1](/packages/contributte-image-storage)[vinelab/cdn

Content Delivery Network (CDN) Package for Laravel

217240.8k1](/packages/vinelab-cdn)[publiux/laravelcdn

Content Delivery Network (CDN) Package for Laravel

155230.4k](/packages/publiux-laravelcdn)[ublaboo/image-storage

Image storage for Nette framework

2913.0k](/packages/ublaboo-image-storage)[juhasev/laravelcdn

Content Delivery Network (CDN) Package for Laravel

1820.4k](/packages/juhasev-laravelcdn)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
