R2 CORS Configuration
Configure Cross-Origin Resource Sharing (CORS) policies for R2 buckets to control which origins can access your bucket content from web browsers.
Overview
public class CorsService(ICloudflareApiClient cf)
{
public async Task EnableCorsAsync(string bucket)
{
await cf.Accounts.Buckets.SetCorsAsync(bucket, new BucketCorsPolicy([
new CorsRule(
Allowed: new CorsAllowed(
Methods: ["GET", "HEAD"],
Origins: ["https://example.com"]
)
)
]));
}
}
Note
Jurisdictional Buckets: If your bucket was created with a jurisdiction (e.g., R2Jurisdiction.EuropeanUnion), you must pass the jurisdiction parameter to all CORS operations. See Working with Jurisdictional Buckets.
await cf.Accounts.Buckets.SetCorsAsync("my-eu-bucket", corsPolicy, R2Jurisdiction.EuropeanUnion);
Setting CORS Policy
Basic CORS for Web Access
await cf.Accounts.Buckets.SetCorsAsync("my-bucket", new BucketCorsPolicy([
new CorsRule(
Allowed: new CorsAllowed(
Methods: ["GET", "HEAD"],
Origins: ["https://example.com", "https://www.example.com"]
)
)
]));
Allow All Origins
await cf.Accounts.Buckets.SetCorsAsync("my-bucket", new BucketCorsPolicy([
new CorsRule(
Allowed: new CorsAllowed(
Methods: ["GET", "HEAD"],
Origins: ["*"]
)
)
]));
Full CRUD Access
await cf.Accounts.Buckets.SetCorsAsync("my-bucket", new BucketCorsPolicy([
new CorsRule(
Allowed: new CorsAllowed(
Methods: ["GET", "HEAD", "PUT", "POST", "DELETE"],
Origins: ["https://app.example.com"],
Headers: ["Content-Type", "Authorization", "X-Custom-Header"]
),
ExposeHeaders: ["ETag", "Content-Length"],
MaxAgeSeconds: 3600
)
]));
Multiple Rules
await cf.Accounts.Buckets.SetCorsAsync("my-bucket", new BucketCorsPolicy([
// Read-only for public site
new CorsRule(
Id: "public-read",
Allowed: new CorsAllowed(
Methods: ["GET", "HEAD"],
Origins: ["https://www.example.com"]
),
MaxAgeSeconds: 86400
),
// Full access for admin
new CorsRule(
Id: "admin-full",
Allowed: new CorsAllowed(
Methods: ["GET", "HEAD", "PUT", "POST", "DELETE"],
Origins: ["https://admin.example.com"],
Headers: ["*"]
)
)
]));
Getting CORS Policy
var policy = await cf.Accounts.Buckets.GetCorsAsync("my-bucket");
foreach (var rule in policy.Rules)
{
Console.WriteLine($"Rule: {rule.Id ?? "unnamed"}");
Console.WriteLine($" Origins: {string.Join(", ", rule.Allowed.Origins)}");
Console.WriteLine($" Methods: {string.Join(", ", rule.Allowed.Methods)}");
}
Deleting CORS Policy
await cf.Accounts.Buckets.DeleteCorsAsync("my-bucket");
Models Reference
BucketCorsPolicy
| Property |
Type |
Description |
Rules |
IReadOnlyList<CorsRule> |
List of CORS rules |
CorsRule
| Property |
Type |
Description |
Allowed |
CorsAllowed |
Allowed origins, methods, headers |
Id |
string? |
Optional rule identifier |
ExposeHeaders |
IReadOnlyList<string>? |
Headers accessible to browser |
MaxAgeSeconds |
int? |
Preflight cache duration |
CorsAllowed
| Property |
Type |
Description |
Methods |
IReadOnlyList<string> |
HTTP methods (GET, PUT, etc.) |
Origins |
IReadOnlyList<string> |
Allowed origins (* for all) |
Headers |
IReadOnlyList<string>? |
Allowed request headers |
HTTP Methods
| Method |
Description |
GET |
Download objects |
HEAD |
Get object metadata |
PUT |
Upload objects |
POST |
Multipart uploads |
DELETE |
Delete objects |
Common Patterns
CDN Configuration
public async Task ConfigureCdnCorsAsync(string bucket, string cdnDomain)
{
await cf.Accounts.Buckets.SetCorsAsync(bucket, new BucketCorsPolicy([
new CorsRule(
Allowed: new CorsAllowed(
Methods: ["GET", "HEAD"],
Origins: [$"https://{cdnDomain}"]
),
ExposeHeaders: ["Content-Length", "Content-Type", "ETag"],
MaxAgeSeconds: 86400 // 24 hours
)
]));
}
Development and Production
public async Task SetupCorsAsync(string bucket, bool isDevelopment)
{
var origins = isDevelopment
? new[] { "http://localhost:3000", "http://localhost:5173" }
: new[] { "https://app.example.com" };
await cf.Accounts.Buckets.SetCorsAsync(bucket, new BucketCorsPolicy([
new CorsRule(
Allowed: new CorsAllowed(
Methods: ["GET", "HEAD", "PUT", "DELETE"],
Origins: origins,
Headers: ["Content-Type"]
)
)
]));
}
Direct Upload from Browser
// Enable CORS for presigned URL uploads
await cf.Accounts.Buckets.SetCorsAsync("uploads", new BucketCorsPolicy([
new CorsRule(
Allowed: new CorsAllowed(
Methods: ["PUT"],
Origins: ["https://app.example.com"],
Headers: ["Content-Type", "Content-MD5"]
),
ExposeHeaders: ["ETag"],
MaxAgeSeconds: 3600
)
]));
Required Permissions
| Permission |
Scope |
Level |
| Workers R2 Storage |
Account |
Write |
Security Considerations
- Avoid
* origins in production - Use specific domains
- Limit methods - Only allow methods you need
- Set MaxAgeSeconds - Cache preflight to reduce requests
- Review headers - Only expose necessary headers