TL;DR: The Cloudflare.NET SDK provides
IR2Clientfor R2 object storage with strongly-typed APIs, native dependency injection, automatic multipart uploads, and built-in operation metrics.
R2 is Cloudflare's object storage with zero egress fees. Unlike AWS S3 or Azure Blob Storage, you don't pay to download your own data.
While R2 offers S3 API compatibility and .NET developers can use the AWS SDK, the Cloudflare.NET SDK provides a streamlined .NET experience with IntelliSense, dependency injection, and automatic handling of R2-specific behaviors.
What the SDK Handles for You
When working with R2's S3-compatible API directly, there are a few things you need to know. The SDK handles these automatically:
Payload Signing Configuration
R2 requires specific S3 client settings. The official Cloudflare documentation specifies that payload signing must be disabled:
var request = new PutObjectRequest
{
BucketName = bucketName,
Key = objectKey,
InputStream = stream,
DisablePayloadSigning = true,
DisableDefaultChecksumValidation = true
};
The SDK configures this automatically, so you never need to remember these flags.
Multipart Upload Rules
R2 has specific rules for multipart uploads:
| Constraint | Value |
|---|---|
| Minimum part size | 5 MiB (except the last part) |
| Maximum part size | 5 GiB |
| Maximum total size | 5 TiB |
| Maximum parts | 10,000 |
The key rules to be aware of:
- All parts except the last must be at least 5 MiB.
- All parts except the last must be the same size. R2 requires uniform part sizes.
- Only the last part can be smaller than 5 MiB.
- The last part cannot be larger than the other parts.
The SDK validates these constraints automatically and calculates optimal part sizes for you.
Endpoint and Region Configuration
R2 uses a specific endpoint format and the region value "auto":
var config = new AmazonS3Config
{
ServiceURL = $"https://{accountId}.r2.cloudflarestorage.com",
ForcePathStyle = true,
AuthenticationRegion = "auto"
};
The SDK constructs this from your configuration automatically.
Operation Metrics
R2 has a specific pricing model based on operation classes:
- Class A operations (writes, lists): $4.50 per million
- Class B operations (reads): $0.36 per million
- Delete operations: Free
The SDK tracks these automatically. Every operation returns an R2Result with operation counts and bytes transferred.
Key Features
Here's what the SDK provides:
Strongly-Typed APIs
Full IntelliSense with XML documentation. Enums and typed models guide you through the API:
var result = await r2.UploadAsync(
bucketName: "my-bucket",
objectKey: "path/to/file",
filePath: "/local/file.pdf",
partSize: 50 * 1024 * 1024);
Simplified Configuration
Native dependency injection with appsettings.json:
builder.Services.AddCloudflareR2Client(builder.Configuration);
Production-Ready Resilience
The core API client includes automatic retries with exponential backoff, circuit breaker, and rate limiting via Polly:
builder.Services.AddCloudflareApiClient(builder.Configuration);
Operation Metrics
Every operation returns Class A/B counts and bytes transferred for cost monitoring:
var result = await r2.UploadAsync(bucket, key, filePath);
Console.WriteLine($"Class A ops: {result.ClassAOperations}");
Console.WriteLine($"Ingress: {result.IngressBytes} bytes");
Additional Features
- Smart upload selection: Auto-chooses single-part vs. multipart based on file size
- Multipart validation: Enforces R2's part size rules before API calls
- Typed exceptions: Clear error information with partial metrics
- Presigned URLs: Generate secure URLs for direct browser uploads
Installation
dotnet add package Cloudflare.NET.Api
dotnet add package Cloudflare.NET.R2
Configuration
appsettings.json
{
"Cloudflare": {
"AccountId": "your-cloudflare-account-id"
},
"R2": {
"AccessKeyId": "your-r2-access-key-id",
"SecretAccessKey": "your-r2-secret-access-key"
}
}
⚠️ Security Note: Never commit secrets to source control. Use User Secrets for development and environment variables or Azure Key Vault for production.
Dependency Injection Setup
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCloudflareApiClient(builder.Configuration);
builder.Services.AddCloudflareR2Client(builder.Configuration);
var app = builder.Build();
Basic Operations
Uploading Files
public class StorageService(IR2Client r2)
{
public async Task<R2Result> UploadAsync(string bucket, string key, string filePath)
{
return await r2.UploadAsync(bucket, key, filePath);
}
}
Upload from a stream:
await using var stream = File.OpenRead(filePath);
await r2.UploadAsync(bucket, key, stream);
Downloading Files
// To file
await r2.DownloadFileAsync(bucket, key, destinationPath);
// To stream
await r2.DownloadFileAsync(bucket, key, outputStream);
Deleting Objects
// Single delete (free)
await r2.DeleteObjectAsync(bucket, "documents/old-file.pdf");
// Batch delete (up to 1,000)
await r2.DeleteObjectsAsync(bucket, new[] { "temp/file1.txt", "temp/file2.txt" });
// Clear bucket
await r2.ClearBucketAsync(bucket);
Multipart Uploads
The SDK automatically handles multipart uploads based on file size. You don't need to think about part sizing, R2's uniform-size requirement, or cleanup on failure.
// Upload a 2 GB file. Multipart happens automatically.
var result = await r2.UploadAsync(bucket, "backups/large-database.bak", filePath);
Custom Part Size
Force multipart and specify part size (clamped to 5 MiB - 5 GiB):
await r2.UploadMultipartAsync(bucket, key, filePath, partSize: 100 * 1024 * 1024);
Automatic Abort on Failure
If a multipart upload fails, the SDK automatically aborts to clean up orphaned parts:
try
{
await r2.UploadMultipartAsync(bucket, key, filePath);
}
catch (CloudflareR2OperationException ex)
{
Console.WriteLine($"Upload failed: {ex.Message}");
Console.WriteLine($"Partial metrics: {ex.PartialMetrics}");
}
Presigned URLs
Generate presigned URLs for direct client uploads:
var url = r2.CreatePresignedPutUrl(bucket, new PresignedPutRequest(
Key: "uploads/document.pdf",
ExpiresAfter: TimeSpan.FromMinutes(15),
ContentLength: 10 * 1024 * 1024,
ContentType: "application/pdf"
));
Multipart Presigned URLs
For large client-side uploads, generate URLs for each part:
// Initiate multipart upload
var initResult = await r2.InitiateMultipartUploadAsync(bucket, key);
var uploadId = initResult.Data;
// Generate URLs for all parts
var urls = r2.CreatePresignedUploadPartsUrls(bucket, new PresignedUploadPartsRequest(
Key: key,
UploadId: uploadId,
PartNumbers: Enumerable.Range(1, partCount).ToArray(),
ExpiresAfter: TimeSpan.FromHours(1),
ContentLength: partSize,
ContentType: "application/octet-stream"
));
The SDK validates part sizes before generating URLs, catching R2's uniform-size requirement early rather than after clients attempt uploads.
Error Handling
The SDK provides typed exceptions with partial metrics:
try
{
await r2.UploadAsync(bucket, key, filePath);
}
catch (CloudflareR2OperationException ex)
{
Console.WriteLine($"Failed: {ex.Message}");
Console.WriteLine($"Partial metrics: {ex.PartialMetrics}");
}
catch (CloudflareR2BatchException<string> ex)
{
Console.WriteLine($"Failed keys: {string.Join(", ", ex.FailedItems)}");
}
Listing Objects
var result = await r2.ListObjectsAsync(bucket, prefix: "documents/");
foreach (var obj in result.Data)
{
Console.WriteLine($"{obj.Key}: {obj.Size} bytes");
}
The SDK handles continuation tokens automatically.
Complete Example: File Upload API
A minimal file upload API using ASP.NET Core:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCloudflareApiClient(builder.Configuration);
builder.Services.AddCloudflareR2Client(builder.Configuration);
var app = builder.Build();
app.MapPost("/upload/{bucket}/{*key}", async (
string bucket, string key, IFormFile file, IR2Client r2) =>
{
await using var stream = file.OpenReadStream();
var result = await r2.UploadAsync(bucket, key, stream);
return Results.Ok(new { key, result.IngressBytes, result.ClassAOperations });
});
app.MapGet("/download/{bucket}/{*key}", async (
string bucket, string key, IR2Client r2, HttpResponse response) =>
{
response.ContentType = "application/octet-stream";
await r2.DownloadFileAsync(bucket, key, response.Body);
});
app.MapDelete("/{bucket}/{*key}", async (
string bucket, string key, IR2Client r2) =>
{
await r2.DeleteObjectAsync(bucket, key);
return Results.NoContent();
});
app.Run();
Multi-Account Support
Register named clients for multiple accounts:
builder.Services.AddCloudflareR2Client("production", options =>
{
options.AccessKeyId = "prod-access-key";
options.SecretAccessKey = "prod-secret-key";
});
builder.Services.AddCloudflareR2Client("staging", options =>
{
options.AccessKeyId = "staging-access-key";
options.SecretAccessKey = "staging-secret-key";
});
Use via factory or keyed services (.NET 8+):
// Factory pattern
public class MigrationService(IR2ClientFactory factory)
{
public async Task MigrateAsync(string bucket, string key)
{
var staging = factory.CreateClient("staging");
var production = factory.CreateClient("production");
using var stream = new MemoryStream();
await staging.DownloadFileAsync(bucket, key, stream);
stream.Position = 0;
await production.UploadAsync(bucket, key, stream);
}
}
// Keyed services
public class ProductionService([FromKeyedServices("production")] IR2Client r2)
{
public async Task UploadAsync(string bucket, string key, Stream data)
=> await r2.UploadAsync(bucket, key, data);
}
Bucket Management
The Cloudflare.NET.Api package provides bucket operations via the REST API for end-to-end workflows:
// Create a bucket with location hint
var bucket = await cf.Accounts.Buckets.CreateAsync("my-bucket", R2LocationHint.WestEurope);
// Get bucket details
var info = await cf.Accounts.Buckets.GetAsync("my-bucket");
// List all buckets
await foreach (var b in cf.Accounts.Buckets.ListAllAsync())
{
Console.WriteLine($"{b.Name} - {b.Location}");
}
// Delete bucket (must be empty)
await r2.ClearBucketAsync("my-bucket");
await cf.Accounts.Buckets.DeleteAsync("my-bucket");
This enables complete infrastructure-as-code scenarios where you provision buckets, manage objects, and tear down resources programmatically.
Summary
The Cloudflare.NET SDK provides a streamlined R2 experience for .NET developers:
| Feature | What You Get |
|---|---|
| Strongly-Typed APIs | Full IntelliSense with XML documentation |
| Simplified Configuration | One-line DI registration from appsettings.json
|
| Production-Ready Resilience | Automatic retries, circuit breaker, and rate limiting |
| Operation Metrics | Class A/B counts and bytes transferred |
| Automatic Multipart | Part sizing, validation, and orchestration |
| Bucket Management | Create, list, and delete buckets via REST API |
Get started:
dotnet add package Cloudflare.NET.Api
dotnet add package Cloudflare.NET.R2
Learn more:
Cloudflare.NET is an open-source, community-maintained SDK. Contributions are welcome!
Top comments (0)