---
title: "How to Generate Pre-signed URLs for Cloudflare R2 with Astro on Cloudflare Workers"
description: "How to generate pre-signed URLs to retrieve and upload to Cloudflare R2 with Astro on Cloudflare Workers?"
source: "https://www.launchfa.st/blog/cloudflare-r2-storage-cloudflare-workers"
author: "Rishi Raj Jain"
created_at: 2025-04-23T12:00:00.000Z
updated_at: 2026-07-02T00:00:00.000Z
---

> **Site index for agents**
> Fetch https://www.launchfa.st/llms.txt to discover every page on this site.
> Every page is also available as Markdown by appending `.md` to its URL.

<img width="1600" height="900" alt="How to Generate Pre-signed URLs for Cloudflare R2 with Cloudflare Workers" decoding="async" loading="eager" class="mt-4 border rounded-sm bg-cover bg-center bg-no-repeat transform will-change-auto" src="https://ik.imagekit.io/vjeqenuhn/launchfast-website/cloudflare-r2-storage-cloudflare-workers.png" />

<a href="/#pricing" class="rounded-full break-words text-xs border text-black px-4 py-2 max-w-max text-center no-underline hover:bg-black hover:text-white">
  ℹ️&nbsp;&nbsp;Available today with LaunchFast Starter Kits</a>
</div>

In this guide, you will learn how to generate pre-signed URLs for Cloudflare R2 with Astro on Cloudflare Workers. You will go through the process of setting up a new Astro project, enabling server-side rendering using the Cloudflare adapter, obtaining Cloudflare R2 credentials and then creating functions to generate pre-signed URLs for retrieval and upload from Cloudflare R2.

### Prerequisites

To follow along, you will need:

- [Node.js 20](https://nodejs.org/en) or later
- A [Cloudflare](https://cloudflare.com) account

## Table Of Contents

- [Create a new Astro application](#create-a-new-astro-application)
- [Integrate Cloudflare adapter in your Astro project](#integrate-cloudflare-adapter-in-your-astro-project)
- [Setting Up Cloudflare R2](#setting-up-cloudflare-r2)
- [Generate the pre-signed URLs](#generate-the-pre-signed-urls)
  - [1. Access the Environment Variables](#1-access-the-environment-variables)
    - [2. Define the AWS Client](#2-define-the-aws-client)
    - [3. Determine R2 URLs](#3-determine-r2-urls)
    - [4. Pre-signed URL to GET a R2 Object (retrieve)](#4-pre-signed-url-to-get-a-r2-object-retrieve)
    - [5. Pre-signed URL to PUT a R2 Object (upload)](#5-pre-signed-url-to-put-a-r2-object-upload)
    - [6. Create a Server Endpoint (an API Route) in Astro](#6-create-a-server-endpoint-an-api-route-in-astro)
- [Deploy to Cloudflare Workers](#deploy-to-cloudflare-workers)

## Create a new Astro application

Let’s get started by creating a new Astro project. Open your terminal and run the following command:

```bash
npm create astro@latest my-app
```

`npm create astro` is the recommended way to scaffold an Astro project quickly.

When prompted, choose:

- `Use minimal (empty) template` when prompted on how to start the new project.
- `Yes` when prompted to install dependencies.
- `Yes` when prompted to initialize a git repository.

Once that’s done, you can move into the project directory and start the app:

```bash
cd my-app
npm run dev
```

The app should be running on [localhost:4321](http://localhost:4321/). Next, execute the command below to install the necessary library for building the application:

```bash
npm install aws4fetch
```

The following library is installed:

- `aws4fetch`: An AWS client for environments that support fetch and SubtleCrypto.

## Integrate Cloudflare adapter in your Astro project

To generate pre-signed URLs for each object dynamically, you will enable server-side rendering in your Astro project via the Cloudflare adapter, execute the following command:

```bash
npx astro add cloudflare
```

When prompted, choose the following:

- `Y` when prompted whether to install the Cloudflare dependencies.
- `Y` when prompted whether to make changes to the Astro configuration file.

You have successfully enabled server-side rendering in Astro.

To make sure that the output is deployable to Cloudflare Workers, create a `wrangler.toml` file in the root of the project with the following code:

```toml
// File: wrangler.toml

name = "cloudflare-r2-astro-workers"
main = "dist/_worker.js"
compatibility_date = "2025-04-01"
compatibility_flags = [ "nodejs_compat" ]

[assets]
directory="dist"
binding="ASSETS"

[vars]
AWS_KEY_ID=""
AWS_S3_BUCKET_NAME=""
AWS_SECRET_ACCESS_KEY=""
CLOUDFLARE_R2_ACCOUNT_ID=""
```

Post that, make sure that you have both an `.env` file and a `wrangler.toml` file with the variables defined so that they can be accessed during `npm run dev` and when deployed on Cloudflare Workers respectively.

Further, update the `astro.config.mjs` file with the following to be able to access these variables in code programmatically:

```tsx
// File: astro.config.mjs

// ... Existing imports...
import { defineConfig, envField } from 'astro/config'

export default defineConfig({
  env: {
    schema: {
      AWS_KEY_ID: envField.string({ context: 'server', access: 'secret', optional: false }),
      AWS_S3_BUCKET_NAME: envField.string({ context: 'server', access: 'secret', optional: false }),
      AWS_SECRET_ACCESS_KEY: envField.string({ context: 'server', access: 'secret', optional: false }),
      CLOUDFLARE_R2_ACCOUNT_ID: envField.string({ context: 'server', access: 'secret', optional: false }),
    }
  }
  // adapter
})
```

## Setting Up Cloudflare R2

- Navigate to the Cloudflare R2 Overview page.

<img width="2508" height="1100" alt="Head to the Cloudflare R2 Overview" decoding="async" loading="lazy" class="mt-4 border rounded-sm bg-cover bg-center bg-no-repeat transform will-change-auto" src="https://ik.imagekit.io/vjeqenuhn/launchfast-website/cloudflare-r2-storage-cloudflare-workers/4.png" />

- Create a new bucket in Cloudflare R2 and save the name as `AWS_S3_BUCKET_NAME` in the environment variables (`.env` & `wrangler.toml`)

<img width="2502" height="1586" alt="Create a bucket in Cloudflare R2" decoding="async" loading="lazy" class="mt-4 border rounded-sm bg-cover bg-center bg-no-repeat transform will-change-auto" src="https://ik.imagekit.io/vjeqenuhn/launchfast-website/cloudflare-r2-storage-cloudflare-workers/3.png" />

- Access the R2 Account Details in Cloudflare R2.

<img width="2424" height="1326" alt="Open R2 Account Details in Cloudflare R2" decoding="async" loading="lazy" class="mt-4 border rounded-sm bg-cover bg-center bg-no-repeat transform will-change-auto" src="https://ik.imagekit.io/vjeqenuhn/launchfast-website/cloudflare-r2-storage-cloudflare-workers/5.png" />

- Retrieve the Cloudflare R2 Account ID and save it as `CLOUDFLARE_R2_ACCOUNT_ID` in the environment variables (`.env` & `wrangler.toml`)

<img width="2454" height="1322" alt="Grab the Cloudflare R2 Account ID" decoding="async" loading="lazy" class="mt-4 border rounded-sm bg-cover bg-center bg-no-repeat transform will-change-auto" src="https://ik.imagekit.io/vjeqenuhn/launchfast-website/cloudflare-r2-storage-cloudflare-workers/6.png" />

- Create an API token in Cloudflare R2.

<img width="2452" height="1096" alt="Create an API token in Cloudflare R2" decoding="async" loading="lazy" class="mt-4 border rounded-sm bg-cover bg-center bg-no-repeat transform will-change-auto" src="https://ik.imagekit.io/vjeqenuhn/launchfast-website/cloudflare-r2-storage-cloudflare-workers/1.png" />

- Obtain the Access Key ID and Secret Access Key from Cloudflare R2 and save them as `AWS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` in the environment variables (`.env` & `wrangler.toml`) respectively.

<img width="2262" height="516" alt="Grab the Access Key ID and Secret Access Key in Cloudflare R2" decoding="async" loading="lazy" class="mt-4 border rounded-sm bg-cover bg-center bg-no-repeat transform will-change-auto" src="https://ik.imagekit.io/vjeqenuhn/launchfast-website/cloudflare-r2-storage-cloudflare-workers/2.png" />

## Generate the pre-signed URLs

### 1. Access the Environment Variables

The first step is to access the necessary environment variables during the runtime to create an AWS Client via aws4fetch. From [Astro 5.6 and beyond](https://astro.build/blog/astro-560/), the way you want to access runtime environment variables in your code is by using the `getSecret` function from `astro:env/server` to keep things provider agnostic.  This is crucial for storing sensitive information securely without hardcoding it into your application. You'll retrieve the following variables:

- Cloudflare R2 Access Key ID (as `AWS_KEY_ID`)
- Cloudflare Bucket Name (as `AWS_S3_BUCKET_NAME`)
- Cloudflare R2 Secret Access Key (as `AWS_SECRET_ACCESS_KEY`)
- Cloudflare R2 Account ID (as `CLOUDFLARE_R2_ACCOUNT_ID`)

```typescript
// File: src/storage/r2.ts

import { getSecret } from 'astro:env/server'

const accessKeyId = getSecret('AWS_KEY_ID')
const s3BucketName = getSecret('AWS_S3_BUCKET_NAME')
const secretAccessKey = getSecret('AWS_SECRET_ACCESS_KEY')
const r2AccountId = getSecret('CLOUDFLARE_R2_ACCOUNT_ID')
```

#### 2. Define the AWS Client

Next, you’ll define the `defineAws4Fetch` function that creates an AWS client instance. This function checks if the required AWS credentials are set and returns a new `AwsClient` instance configured for R2.

```typescript
// File: src/storage/r2.ts
import { AwsClient } from 'aws4fetch'

// ...Existing Code...

async function defineAws4Fetch(): Promise<AwsClient> {
  if (!accessKeyId || !secretAccessKey) {
    throw new Error(`AWS_KEY_ID OR AWS_SECRET_ACCESS_KEY environment variable(s) are not set.`)
  }
  return new AwsClient({
    accessKeyId,
    secretAccessKey,
    service: 's3',
    region: 'auto',
  })
}
```

#### 3. Determine R2 URLs

You'll need to generate unique URLs for each file upload to Cloudflare R2. The `getR2URL` function below takes care of constructing the correct URL based on the file name and the bucket name.

```typescript
// File: src/storage/r2.ts

// ...Existing Code...

function getR2URL({ Key }: { Key: string }) {
  if (!s3BucketName) {
    throw new Error(`AWS_S3_BUCKET_NAME environment variable(s) are not set.`)
  }
  return new URL(`https://${r2AccountId}.r2.cloudflarestorage.com/${s3BucketName}/${Key}`)
}
```

### 4. Pre-signed URL to GET a R2 Object (retrieve)

The `getR2ObjectURL` function below retrieves an object's pre-signed URL from Cloudflare R2. It generates a signed request that allows you to access the file securely.

```typescript
// File: src/storage/r2.ts

// ...Existing Code...

export async function getR2ObjectURL(Key: string) {
  try {
    const endpointUrl = getR2URL({ Key })
    endpointUrl.searchParams.set('X-Amz-Expires', '3600')
    const client = await defineAws4Fetch()
    const signedRequest = await client.sign(new Request(endpointUrl), { aws: { signQuery: true } })
    return signedRequest.url
  } catch (e: any) {
    const tmp = e.message || e.toString()
    console.log(tmp)
    return
  }
}
```

### 5. Pre-signed URL to PUT a R2 Object (upload)

The `uploadR2ObjectURL` function below is responsible for generating a pre-signed URL for uploading a file to Cloudflare R2. It follows a similar structure to the `getR2ObjectURL` function, generating a signed URL that allows you to upload files securely.

```typescript
export async function uploadR2ObjectURL(file: { name: string; type: string }) {
  try {
    const Key = file.name
    const endpointUrl = getR2URL({ Key })
    endpointUrl.searchParams.set('X-Amz-Expires', '3600')
    const client = await defineAws4Fetch()
    const signedRequest = await client.sign(new Request(endpointUrl, { method: 'PUT', headers: { 'Content-Type': file.type } }), { method: 'PUT', aws: { signQuery: true } })
    return signedRequest.url
  } catch (e: any) {
    const tmp = e.message || e.toString()
    console.log(tmp)
    return
  }
}
```

### 6. Create a Server Endpoint (an API Route) in Astro

```tsx
// File: src/pages/api/storage.ts

import type { APIContext } from 'astro'
import { getR2ObjectURL, uploadR2ObjectURL } from '../../storage/r2'

// Define an asynchronous function named GET that accepts a request object.
export async function GET({ request }: APIContext) {
  // Extract the 'file' parameter from the request URL.
  const url = new URL(request.url)
  const file = url.searchParams.get('file')
  // Check if the 'file' parameter exists in the URL.
  if (file) {
    try {
      const filePublicURL = await getR2ObjectURL(file)
      // Return a response with the image's public URL and a 200 status code.
      return new Response(filePublicURL)
    } catch (error: any) {
      // If an error occurs, log the error message and return a response with a 500 status code.
      const message = error.message || error.toString()
      console.log(message)
      return new Response(message, { status: 500 })
    }
  }
  // If the 'file' parameter is not found in the URL, return a response with a 400 status code.
  return new Response('Invalid Request.', { status: 400 })
}

export async function POST({ request }: APIContext) {
    // Extract the 'file' parameter from the request URL.
    const url = new URL(request.url)
    const type = url.searchParams.get('type')
    const name = url.searchParams.get('name')
    if (!type || !name) return new Response('Invalid Request.', {status:400})
    try {
      // Generate an accessible URL for the uploaded file
      // Use this url to perform a GET to this endpoint with file query param valued as below
      const publicUploadUrl = await uploadR2ObjectURL({ type, name })
      // Return a success response with a message
      return new Response(publicUploadUrl)
    } catch (error: any) {
      // If there was an error during the upload process, return a 403 response with the error message
      const message = error.message || error.toString()
      console.log(message)
      return new Response(message, { status: 500 })
    }
}
```

## Deploy to Cloudflare Workers

To make your application deployable to Cloudflare Workers, create a file named `.assetsignore` in the `public` directory with the following content:

```
_routes.json
_worker.js
```

Next, you will need to use the Wrangler CLI to deploy your application to Cloudflare Workers. Run the following command to deploy:

```bash
npm run build && npx wrangler@latest deploy
```

## References

- [GitHub Repository](https://github.com/rishi-raj-jain/cloudflare-r2-astro-workers)

### Conclusion

In this blog post, you learned how to integrate Cloudflare R2 with Astro and Cloudflare Workers for file uploads and retrieval. By following the implementation steps, you can securely upload and retrieve files from Cloudflare R2, ensuring that your web application has a robust and flexible storage solution.

If you would like to explore specific sections in more detail, expand on certain concepts, or cover additional related topics, please let me know, and I'll be happy to assist!
