INTEGRITY Cloudflare Docs

Presigned URLs

Presigned URLs are an S3 concept 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 server-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 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

SDK examples

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 },
);
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
)
# 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:

Best practices

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

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.

# 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:

Supported operations

R2 supports presigned URLs for the following HTTP methods:

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 (requires Pro plan or above).

R2 API tokens

Create credentials for generating presigned URLs.

Public buckets

Alternative approach for public read access without authentication.

R2 bindings in Workers

Alternative for server-side R2 access with built-in authentication.

Storing user generated content

Architecture guide for handling user uploads with R2.