An R2 object proxy built with Cloudflare Workers. It serves files by URL path with configurable CORS and MIME handling.
The Worker reads objects from an R2 bucket binding and returns them through HTTP. The default bucket is workers-bucket.
Features:
- Serves
index.htmlfor requests to/and uses404.htmlas a not-found response when present. - Supports
GET,HEAD, andOPTIONSrequests. - Applies CORS settings from the
CORS_CONFIGruntime variable. - Resolves MIME types from the
MIME_TYPESruntime variable, then falls back to R2 object metadata.
CORS controls browser cross-origin access; it is not authentication. Without an additional access-control layer, anyone who knows an object path can request it through the Worker.
- Node.js
24.18.0or newer - pnpm
12.3.4 - An existing R2 bucket in your Cloudflare account
Node.js and pnpm versions are pinned in package.json.
pnpm install
pnpm run devLocal development uses wrangler.jsonc, including the default workers-bucket binding.
Build and deployment are separate stages:
pnpm run buildreadswrangler.jsonc, selects the bucket name, and generateswrangler.deploy.jsonc. It does not upload the Worker.pnpm run deployruns Wrangler with the generated configuration. Run the build first.
To change the bucket without modifying the repository, set R2_BUCKET_NAME as a Build variable in Cloudflare Workers Builds. To add custom domains, set the optional CUSTOM_DOMAINS build variable to a comma-separated list of hostnames. Do not include URL schemes. These are build variables, not Worker runtime variables.
Configure Workers Builds as follows:
| Setting | Value |
|---|---|
| Build command | pnpm run build |
| Deploy command | pnpm run deploy |
| Build variables | R2_BUCKET_NAME=<bucket-name> and optionally CUSTOM_DOMAINS=files.example.com,cdn.example.com |
Run the same flow locally:
R2_BUCKET_NAME=assets-prod pnpm run build
pnpm run deployIf R2_BUCKET_NAME is unset, the build uses the default workers-bucket. If the variable is set but empty, the build fails to prevent deployment to an unintended bucket. If CUSTOM_DOMAINS is unset, no custom-domain routes are added; when set, each hostname is added as a Wrangler custom-domain route. The domains must be eligible custom domains in your Cloudflare account. wrangler.deploy.jsonc is generated during the build and is ignored by Git.
Bindings and default values are defined in wrangler.jsonc:
| Name | Type | Purpose |
|---|---|---|
R2_BUCKET |
R2 binding | The R2 object binding used by the Worker. Default bucket: workers-bucket. |
R2_BUCKET_NAME |
Workers Builds build variable | Selects the bucket during the build and updates the binding in the generated configuration. |
CUSTOM_DOMAINS |
Optional Workers Builds build variable | Comma-separated hostnames added as custom-domain routes in the generated deployment configuration. |
CORS_CONFIG |
JSON runtime variable | Allowed origins, methods, headers, and preflight cache duration. |
MIME_TYPES |
JSON runtime variable | Maps file extensions to Content-Type values. |
On the first deployment, CORS_CONFIG and MIME_TYPES receive their defaults from wrangler.jsonc. keep_vars: true preserves runtime values changed through the Dashboard on subsequent Wrangler deployments. These variables are not build variables and do not select the bucket.
Workers Cache is enabled for HTTP GET and HEAD requests. Object responses use their R2 Cache-Control metadata when present; otherwise, the Worker defaults to public, max-age=604800 (seven days). Replacing an object at the same URL may therefore take up to seven days to appear from cache unless the cache is purged. Prefer versioned URLs for frequently updated assets.
| Command | Purpose |
|---|---|
pnpm run dev |
Run the Worker locally with Wrangler. |
pnpm run build |
Generate wrangler.deploy.jsonc. |
pnpm run deploy |
Deploy wrangler.deploy.jsonc. |
pnpm test -- --run |
Run the test suite once. |
pnpm run cf-typegen |
Regenerate binding types from the Wrangler configuration. |
This project is licensed under the MIT License.