# How to Implement CORS Policies for R2 Storage Buckets in Next.js Cloudflare Workers

> Learn to implement CORS policies for R2 storage buckets in Next.js Cloudflare Workers. Manually add headers to your Worker response to enable cross origin resource sharing.

- Repository: [Muhammad Arifin/fullstack-next-cloudflare](https://github.com/ifindev/fullstack-next-cloudflare)
- Tags: how-to-guide
- Published: 2026-03-04

---

**To implement CORS policies for Cloudflare R2 storage buckets, you must manually inject `Access-Control-Allow-Origin` and related headers into the Worker response that serves the object, since R2 does not provide a native CORS configuration interface like AWS S3.**

Unlike **AWS S3**, **Cloudflare R2** does not expose a UI or API for bucket-level **CORS** configuration. In the `ifindev/fullstack-next-cloudflare` repository, you implement **CORS policies for R2 storage buckets** by attaching headers at the edge **Worker** level before streaming objects to the browser. This approach leverages the `next_cf_app_bucket` binding declared in [`worker-configuration.d.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/worker-configuration.d.ts) and the helper functions in [`src/lib/r2.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/lib/r2.ts) to serve files with strict security controls.

## Understanding the R2 CORS Architecture

Cloudflare R2 stores objects without evaluating CORS rules during retrieval. To implement CORS policies for R2 storage buckets, you intercept the request in your **Next.js** API route or **Worker**, fetch the object via `env.next_cf_app_bucket.get()`, and construct a new `Response` that includes the necessary CORS headers. This grants you full programmatic control over which origins can access your stored assets.

## Creating a Reusable CORS Utility

Reusable logic prevents header inconsistencies across endpoints. Create [`src/lib/cors.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/lib/cors.ts) to generate standardized headers for every R2-serving route.

```typescript
// src/lib/cors.ts
export function corsHeaders(origin = "*"): HeadersInit {
  return {
    "Access-Control-Allow-Origin": origin,
    "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS",
    "Access-Control-Allow-Headers": "Content-Type, Authorization",
    "Access-Control-Max-Age": "86400", // 24 hours
  };
}

```

This helper returns the headers required for pre-flight and actual requests, allowing you to whitelist specific origins or permit global access.

## Serving R2 Objects with CORS Headers

The repository’s R2 interaction layer resides in [`src/lib/r2.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/lib/r2.ts), which exports `getFromR2` for object retrieval. To implement CORS policies for R2 storage buckets in a public API, create a dynamic route at `src/app/api/files/[...key]/route.ts` that wraps the R2 object in a CORS-enabled response.

### Handling GET Requests

```typescript
// src/app/api/files/[...key]/route.ts
import { getFromR2 } from "@/lib/r2";
import { corsHeaders } from "@/lib/cors";

export async function GET(request: Request, { params }: { params: { key: string[] } }) {
  const key = params.key.join("/");
  const object = await getFromR2(key);
  
  if (!object) {
    return new Response("Not found", { status: 404 });
  }

  const headers = new Headers({
    "Content-Type": object.httpMetadata?.contentType ?? "application/octet-stream",
    ...corsHeaders(),
  });

  return new Response(object.body, { status: 200, headers });
}

```

The `getFromR2` function retrieves the `R2ObjectBody` from the binding defined in [`worker-configuration.d.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/worker-configuration.d.ts), and the spread operator injects the CORS headers into the final response.

### Handling Pre-flight OPTIONS Requests

Browsers send an `OPTIONS` request before cross-origin GET calls. You must explicitly handle this to implement CORS policies for R2 storage buckets correctly.

```typescript
// src/app/api/files/[...key]/route.ts (continued)
export async function OPTIONS() {
  return new Response(null, {
    status: 204,
    headers: corsHeaders(),
  });
}

```

Returning a 204 No Content response with the same headers satisfies the CORS pre-flight check without streaming unnecessary data.

## Applying CORS to Existing Upload Workflows

If your application already uses `uploadToR2` from [`src/lib/r2.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/lib/r2.ts) to store files, you can extend those modules to serve files with restricted origins. Import `corsHeaders` into any existing route and apply it when returning R2 objects:

```typescript
return new Response(object.body, {
  status: 200,
  headers: {
    "Content-Type": object.httpMetadata?.contentType ?? "application/octet-stream",
    ...corsHeaders("https://my-frontend.example.com"),
  },
});

```

This restricts access to a specific frontend domain while maintaining the streaming performance of R2.

## Summary

- Cloudflare R2 does not provide native CORS configuration; you must implement CORS policies for R2 storage buckets at the Worker level using response headers.
- Create a centralized `corsHeaders` utility in [`src/lib/cors.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/lib/cors.ts) to ensure consistent security policies across endpoints.
- Use `getFromR2` from [`src/lib/r2.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/lib/r2.ts) to fetch objects and wrap them in a `Response` with CORS headers.
- Always export an `OPTIONS` handler that returns a 204 response with CORS headers to support browser pre-flight requests.
- Reference the `next_cf_app_bucket` binding declared in [`worker-configuration.d.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/worker-configuration.d.ts) when accessing bucket resources.

## Frequently Asked Questions

### Does Cloudflare R2 support bucket-level CORS configuration like AWS S3?

No. According to the source code analysis, R2 does not expose a native CORS configuration UI or API. To implement CORS policies for R2 storage buckets, you must attach `Access-Control-Allow-Origin` and related headers in the Worker or API route that serves the file, as demonstrated in the `src/app/api/files/[...key]/route.ts` implementation.

### Where is the R2 bucket binding defined in the fullstack-next-cloudflare project?

The bucket binding is declared in [`worker-configuration.d.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/worker-configuration.d.ts) as `next_cf_app_bucket`. This binding is used throughout the application, particularly in [`src/lib/r2.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/lib/r2.ts) where the `getFromR2` and `uploadToR2` functions interact with the R2 storage bucket.

### How do I restrict CORS to a specific origin instead of allowing all domains?

Pass a specific origin string to the `corsHeaders` function instead of the default `"*"` wildcard. For example, use `corsHeaders("https://my-frontend.example.com")` when constructing the response headers in your API route. This ensures only requests from that specific origin can access the R2-stored assets.

### Why is an OPTIONS handler required for CORS implementation?

Browsers perform a pre-flight check using an `OPTIONS` request before executing cross-origin GET or POST requests. When you implement CORS policies for R2 storage buckets, you must handle this by returning a 204 No Content response with the appropriate CORS headers, as shown in the route handler example, otherwise the browser blocks the subsequent request.