---
title: Presigned URLs
description: Generate presigned URLs to grant temporary access to R2 objects without exposing credentials.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/r2/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Presigned URLs

Presigned URLs are an [S3 concept ↗](https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html) for granting temporary access to objects without exposing your API credentials. A presigned URL includes signature parameters in the URL itself, authorizing anyone with the URL to perform a specific operation (like `GetObject` or `PutObject`) on a specific object until the URL expires.

They are ideal for granting temporary access to specific objects, such as allowing users to upload files directly to R2 or providing time-limited download links.

To generate a presigned URL, you specify:

1. **Resource identifier**: Account ID, bucket name, and object path
2. **Operation**: The S3 API operation permitted (GET, PUT, HEAD, or DELETE)
3. **Expiry**: Timeout from 1 second to 7 days (604,800 seconds)

Presigned URLs are generated client-side with no communication with R2, requiring only your R2 API credentials and an implementation of the AWS Signature Version 4 signing algorithm.

## Choosing an approach

R2 supports two patterns for time-limited access. They overlap but have different trade-offs:

| Pattern                                                                                     | Grants                                                                                                         | Good for                                                                                                                   |
| ------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| Presigned URLs (this page)                                                                  | A single S3 operation on a single object                                                                       | Granting direct HTTP access to a single object without an S3 client, such as a browser upload or a shareable download link |
| [Temporary credentials](https://developers.cloudflare.com/r2/api/s3/temporary-credentials/) | Multiple S3 operations, scoped to a bucket and a set of permitted operations, and optionally to specific paths | Callers that use a standard S3 client or SDK to perform multiple operations in a scoped session                            |

## Generate a presigned URL

### Prerequisites

* [Account ID](https://developers.cloudflare.com/fundamentals/account/find-account-and-zone-ids/) (for constructing the S3 endpoint URL)
* [R2 API token](https://developers.cloudflare.com/r2/api/tokens/) (Access Key ID and Secret Access Key)
* AWS SDK or compatible S3 client library

### SDK examples

* [ JavaScript ](#tab-panel-7399)
* [ Python ](#tab-panel-7400)
* [ CLI ](#tab-panel-7401)

TypeScript

```

import { S3Client, GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";

import { getSignedUrl } from "@aws-sdk/s3-request-presigner";


const S3 = new S3Client({

  region: "auto", // Required by SDK but not used by R2

  // Provide your Cloudflare account ID

  endpoint: `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`,

  // Retrieve your S3 API credentials for your R2 bucket via API tokens (see: https://developers.cloudflare.com/r2/api/tokens)

  credentials: {

    accessKeyId: '<ACCESS_KEY_ID>',

    secretAccessKey: '<SECRET_ACCESS_KEY>',

  },

});


// Generate presigned URL for reading (GET)

const getUrl = await getSignedUrl(

  S3,

  new GetObjectCommand({ Bucket: "my-bucket", Key: "image.png" }),

  { expiresIn: 3600 }, // Valid for 1 hour

);

// https://my-bucket.<ACCOUNT_ID>.r2.cloudflarestorage.com/image.png?X-Amz-Algorithm=...


// Generate presigned URL for writing (PUT)

// Specify ContentType to restrict uploads to a specific file type

const putUrl = await getSignedUrl(

  S3,

  new PutObjectCommand({

    Bucket: "my-bucket",

    Key: "image.png",

    ContentType: "image/png",

  }),

  { expiresIn: 3600 },

);


```

Python

```

import boto3


s3 = boto3.client(

    service_name="s3",

    # Provide your Cloudflare account ID

    endpoint_url='https://<ACCOUNT_ID>.r2.cloudflarestorage.com',

    # Retrieve your S3 API credentials for your R2 bucket via API tokens (see: https://developers.cloudflare.com/r2/api/tokens)

    aws_access_key_id='<ACCESS_KEY_ID>',

    aws_secret_access_key='<SECRET_ACCESS_KEY>',

    region_name="auto", # Required by SDK but not used by R2

)


# Generate presigned URL for reading (GET)

get_url = s3.generate_presigned_url(

  'get_object',

  Params={'Bucket': 'my-bucket', 'Key': 'image.png'},

  ExpiresIn=3600  # Valid for 1 hour

)

# https://my-bucket.<ACCOUNT_ID>.r2.cloudflarestorage.com/image.png?X-Amz-Algorithm=...


# Generate presigned URL for writing (PUT)

# Specify ContentType to restrict uploads to a specific file type

put_url = s3.generate_presigned_url(

  'put_object',

  Params={

    'Bucket': 'my-bucket',

    'Key': 'image.png',

    'ContentType': 'image/png'

  },

  ExpiresIn=3600

)


```

Terminal window

```

# Generate presigned URL for reading (GET)

# The AWS CLI presign command defaults to GET operations

aws s3 presign --endpoint-url https://<ACCOUNT_ID>.r2.cloudflarestorage.com \

  s3://my-bucket/image.png \

  --expires-in 3600


# Output:

# https://<ACCOUNT_ID>.r2.cloudflarestorage.com/my-bucket/image.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...


# Note: The AWS CLI presign command only supports GET operations.

# For PUT operations, use one of the SDK examples above.


```

For complete examples and additional operations, refer to the SDK-specific documentation:

* [AWS SDK for JavaScript](https://developers.cloudflare.com/r2/examples/aws/aws-sdk-js-v3/#generate-presigned-urls)
* [AWS SDK for Python (Boto3)](https://developers.cloudflare.com/r2/examples/aws/boto3/#generate-presigned-urls)
* [AWS CLI](https://developers.cloudflare.com/r2/examples/aws/aws-cli/#generate-presigned-urls)
* [AWS SDK for Go](https://developers.cloudflare.com/r2/examples/aws/aws-sdk-go/#generate-presigned-urls)
* [AWS SDK for PHP](https://developers.cloudflare.com/r2/examples/aws/aws-sdk-php/#generate-presigned-urls)

### Best practices

When generating presigned URLs, you can limit abuse and misuse by:

* **Restricting Content-Type**: Specify the allowed `Content-Type` in your SDK's parameters. The signature will include this header, so uploads will fail with a `403/SignatureDoesNotMatch` error if the client sends a different `Content-Type` for an upload request.
* **Configuring CORS**: If your presigned URLs will be used from a browser, set up [CORS rules](https://developers.cloudflare.com/r2/buckets/cors/#use-cors-with-a-presigned-url) on your bucket to control which origins can make requests.

## Using a presigned URL

Once generated, use a presigned URL like any HTTP endpoint. The signature is embedded in the URL, so no additional authentication headers are required.

Terminal window

```

# Download using a GET presigned URL

curl "https://my-bucket.<ACCOUNT_ID>.r2.cloudflarestorage.com/image.png?X-Amz-Algorithm=..."


# Upload using a PUT presigned URL

curl -X PUT "https://my-bucket.<ACCOUNT_ID>.r2.cloudflarestorage.com/image.png?X-Amz-Algorithm=..." \

  --data-binary @image.png


```

You can also use presigned URLs directly in web browsers, mobile apps, or any HTTP client. The same presigned URL can be reused multiple times until it expires.

## Presigned URL example

The following is an example of a presigned URL that was created using R2 API credentials and following the AWS Signature Version 4 signing process:

```

https://my-bucket.123456789abcdef0123456789abcdef.r2.cloudflarestorage.com/photos/cat.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=CFEXAMPLEKEY12345%2F20251201%2Fauto%2Fs3%2Faws4_request&X-Amz-Date=20251201T180512Z&X-Amz-Expires=3600&X-Amz-Signature=8c3ac40fa6c83d64b4516e0c9e5fa94c998bb79131be9ddadf90cefc5ec31033&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject


```

In this example, this presigned url performs a `GetObject` on the object `photos/cat.png` within bucket `my-bucket` in the account with id `123456789abcdef0123456789abcdef`. The key signature parameters that compose this presigned URL are:

* `X-Amz-Algorithm`: Identifies the algorithm used to sign the URL.
* `X-Amz-Credential`: Contains information about the credentials used to calculate the signature.
* `X-Amz-Date`: The date and time (in ISO 8601 format) when the signature was created.
* `X-Amz-Expires`: The duration in seconds that the presigned URL remains valid, starting from `X-Amz-Date`.
* `X-Amz-Signature`: The signature proving the URL was signed using the secret key.
* `X-Amz-SignedHeaders`: Lists the HTTP headers that were included in the signature calculation.

Note

The signature parameters (e.g. `X-Amz-Algorithm`, `X-Amz-Credential`, `X-Amz-Date`, `X-Amz-Expires`, `X-Amz-Signature`) cannot be tampered with. Attempting to modify the resource, operation, or expiry will result in a `403/SignatureDoesNotMatch` error.

## Supported operations

R2 supports presigned URLs for the following HTTP methods:

* `GET`: Fetch an object from a bucket
* `HEAD`: Fetch an object's metadata from a bucket
* `PUT`: Upload an object to a bucket
* `DELETE`: Delete an object from a bucket

`POST` (multipart form uploads via HTML forms) is not currently supported.

## Security considerations

Treat presigned URLs as bearer tokens. Anyone with the URL can perform the specified operation until it expires. Share presigned URLs only with intended recipients and consider using short expiration times for sensitive operations.

## Custom domains

Presigned URLs work with the S3 API domain (`<ACCOUNT_ID>.r2.cloudflarestorage.com`) and cannot be used with custom domains.

If you need authentication with R2 buckets accessed via custom domains (public buckets), use the [WAF HMAC validation feature](https://developers.cloudflare.com/ruleset-engine/rules-language/functions/#hmac-validation) (requires Pro plan or above).

## Related resources

[ R2 API tokens ](https://developers.cloudflare.com/r2/api/tokens/) Create credentials for generating presigned URLs. 

[ Public buckets ](https://developers.cloudflare.com/r2/buckets/public-buckets/) Alternative approach for public read access without authentication. 

[ R2 bindings in Workers ](https://developers.cloudflare.com/r2/api/workers/workers-api-usage/) Alternative for server-side R2 access with built-in authentication. 

[ Storing user generated content ](https://developers.cloudflare.com/reference-architecture/diagrams/storage/storing-user-generated-content/) Architecture guide for handling user uploads with R2. 

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/r2/","name":"R2"}},{"@type":"ListItem","position":3,"item":{"@id":"/r2/api/","name":"API"}},{"@type":"ListItem","position":4,"item":{"@id":"/r2/api/s3/","name":"S3"}},{"@type":"ListItem","position":5,"item":{"@id":"/r2/api/s3/presigned-urls/","name":"Presigned URLs"}}]}
```
