Upload a file to R2 using a pre-signed url

Upload a file to R2 using a pre-signed url

In this Article


In this article I’ll show a practical example of how to use a pre-signed url to upload a file to Cloudflare R2. Objects in R2 buckets are private by default. That means the user will need permission to be able to upload a file on the client side. In this case we can grant the user with temporary permission that’s restricted to the specific action we want the user to take using a pre-signed url.

Signing the url

We are going to use the aws4fetch library to sign the url. Keep in mind for this to work you’ll might need to update the cors settings of your bucket. You will need to generate the access_key_id and secret_access_key for your R2 bucket. You’ll also need to bind your bucket to your pages app or cloudflare worker. Here is what the pill code looks like:

Now here is the pill of code:

import { AwsClient } from 'aws4fetch'

export const onRequestPost = async (context: any): Promise<Response> => {
  const { env } = context
  // the bound bucket
  const myBucket = env.MY_BUCKET
  // my cloudflare account id
  const cfAccountId = env.CF_ACCOUNT_ID
  // init the client
  const r2: AwsClient = new AwsClient({
    accessKeyId: env.R2_ACCESS_KEY_ID,
    secretAccessKey: env.R2_SECRET_ACCESS_KEY,

  // allow the user to perform operations under the assets folder in my bucket
  const url = new URL(`https://${cfAccountId}.r2.cloudflarestorage.com/${myBucket}/assets`)

  // Specify a custom expiry for the presigned URL, in seconds
  url.searchParams.set('x-Amz-Expires', '3600')

  // allow the user to perform a PUT (upload) operation
  const mySignedUrl = await r2.sign(
    new Request(url, {
      method: 'PUT',
      aws: { signQuery: true },

  // return the signed url
  return Promise.resolve(new Response(JSON.stringify({ signedUrl: mySignedUrl.url }), { status: 200 }))

Now the client can read the signedUrl from the response body and use it for uploading a file.